From 9bec5967b293d6c23c9d7e9338d8ece4873f6eac Mon Sep 17 00:00:00 2001 From: Andrew Hannam Date: Mon, 18 Feb 2013 17:33:35 +1000 Subject: [PATCH] GADC implementation with demo program Also includes driver for AT91SAM7 cpu --- demos/modules/gadc/gfxconf.h | 105 ++++ demos/modules/gadc/gwinosc.c | 183 +++++++ demos/modules/gadc/gwinosc.h | 89 +++ demos/modules/gadc/main.c | 176 ++++++ demos/modules/gadc/results_264x264.jpg | Bin 0 -> 21130 bytes drivers/gadc/AT91SAM7/gadc_lld.c | 102 ++++ drivers/gadc/AT91SAM7/gadc_lld.mk | 5 + .../AT91SAM7/gadc_lld_board_olimexsam7ex256.h | 46 ++ drivers/gadc/AT91SAM7/gadc_lld_config.h | 78 +++ gfxconf.example.h | 2 - include/gadc/gadc.h | 508 +++++++++--------- include/gadc/lld/gadc_lld.h | 190 +++++++ include/gadc/options.h | 112 ++-- include/gfx_rules.h | 264 ++++----- src/gadc/gadc.c | 503 +++++++++++++++-- 15 files changed, 1888 insertions(+), 475 deletions(-) create mode 100644 demos/modules/gadc/gfxconf.h create mode 100644 demos/modules/gadc/gwinosc.c create mode 100644 demos/modules/gadc/gwinosc.h create mode 100644 demos/modules/gadc/main.c create mode 100644 demos/modules/gadc/results_264x264.jpg create mode 100644 drivers/gadc/AT91SAM7/gadc_lld.c create mode 100644 drivers/gadc/AT91SAM7/gadc_lld.mk create mode 100644 drivers/gadc/AT91SAM7/gadc_lld_board_olimexsam7ex256.h create mode 100644 drivers/gadc/AT91SAM7/gadc_lld_config.h create mode 100644 include/gadc/lld/gadc_lld.h diff --git a/demos/modules/gadc/gfxconf.h b/demos/modules/gadc/gfxconf.h new file mode 100644 index 00000000..58475fba --- /dev/null +++ b/demos/modules/gadc/gfxconf.h @@ -0,0 +1,105 @@ +/** + * This file has a different license to the rest of the GFX system. + * You can copy, modify and distribute this file as you see fit. + * You do not need to publish your source modifications to this file. + * The only thing you are not permitted to do is to relicense it + * under a different license. + */ + +/** + * Copy this file into your project directory and rename it as gfxconf.h + * Edit your copy to turn on the GFX features you want to use. + */ + +#ifndef _GFXCONF_H +#define _GFXCONF_H + +/* GFX sub-systems to turn on */ +#define GFX_USE_GDISP TRUE +#define GFX_USE_TDISP FALSE +#define GFX_USE_GWIN TRUE +#define GFX_USE_GEVENT FALSE +#define GFX_USE_GTIMER TRUE +#define GFX_USE_GINPUT FALSE +#define GFX_USE_GADC TRUE +#define GFX_USE_GAUDIN FALSE +#define GFX_USE_GAUDOUT FALSE +#define GFX_USE_GMISC FALSE + +/* Features for the GDISP sub-system. */ +#define GDISP_NEED_VALIDATION TRUE +#define GDISP_NEED_CLIP TRUE +#define GDISP_NEED_TEXT TRUE +#define GDISP_NEED_CIRCLE FALSE +#define GDISP_NEED_ELLIPSE FALSE +#define GDISP_NEED_ARC FALSE +#define GDISP_NEED_SCROLL FALSE +#define GDISP_NEED_PIXELREAD FALSE +#define GDISP_NEED_CONTROL TRUE +#define GDISP_NEED_MULTITHREAD TRUE +#define GDISP_NEED_ASYNC FALSE +#define GDISP_NEED_MSGAPI FALSE + +/* GDISP - builtin fonts */ +#define GDISP_OLD_FONT_DEFINITIONS FALSE +#define GDISP_INCLUDE_FONT_SMALL FALSE +#define GDISP_INCLUDE_FONT_LARGER FALSE +#define GDISP_INCLUDE_FONT_UI1 FALSE +#define GDISP_INCLUDE_FONT_UI2 TRUE +#define GDISP_INCLUDE_FONT_LARGENUMBERS FALSE + +/* Features for the TDISP subsystem. */ +#define TDISP_NEED_MULTITHREAD FALSE + +/* Features for the GWIN sub-system. */ +#define GWIN_NEED_BUTTON FALSE +#define GWIN_NEED_CONSOLE TRUE +#define GWIN_NEED_GRAPH FALSE + +/* Features for the GEVENT sub-system. */ +#define GEVENT_ASSERT_NO_RESOURCE FALSE + +/* Features for the GTIMER sub-system. */ +/* NONE */ + +/* Features for the GINPUT sub-system. */ +#define GINPUT_NEED_MOUSE FALSE +#define GINPUT_NEED_KEYBOARD FALSE +#define GINPUT_NEED_TOGGLE FALSE +#define GINPUT_NEED_DIAL FALSE + +/* Features for the GADC sub-system. */ +/* NONE */ + +/* Features for the GAUDIN sub-system. */ +/* NONE */ + +/* Features for the GAUDOUT sub-system. */ +/* NONE */ + +/* Features for the GMISC sub-system. */ +#define GMISC_NEED_ARRAYOPS FALSE + +/* Optional Parameters for various sub-systems */ +/* + #define GDISP_MAX_FONT_HEIGHT 16 + #define GEVENT_MAXIMUM_SIZE 32 + #define GEVENT_MAX_SOURCE_LISTENERS 32 + #define GTIMER_THREAD_WORKAREA_SIZE 512 + #define GADC_MAX_LOWSPEED_DEVICES 4 +*/ + +/* Optional Low Level Driver Definitions */ +/* + #define GDISP_USE_CUSTOM_BOARD FALSE + #define GDISP_SCREEN_WIDTH 320 + #define GDISP_SCREEN_HEIGHT 240 + #define GDISP_USE_FSMC + #define GDISP_USE_GPIO + #define GDISP_VMT_NAME1(x) x##YourDriver1 + #define GDISP_VMT_NAME2(x) x##YourDriver2 + #define TDISP_COLUMNS 16 + #define TDISP_ROWS 2 +*/ + +#endif /* _GFXCONF_H */ diff --git a/demos/modules/gadc/gwinosc.c b/demos/modules/gadc/gwinosc.c new file mode 100644 index 00000000..d08fbc0d --- /dev/null +++ b/demos/modules/gadc/gwinosc.c @@ -0,0 +1,183 @@ +/* + ChibiOS/GFX - Copyright (C) 2012, 2013 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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. + + ChibiOS/GFX 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, see . +*/ + +/** + * --------------------------- Our Custom GWIN Oscilloscope --------------- + * + * This GWIN superset implements a simple audio oscilloscope using the GADC high speed device. + */ +#include "ch.h" +#include "hal.h" +#include "gfx.h" + +#include "gwinosc.h" + +/* Include internal GWIN routines so we can build our own superset class */ +#include "gwin/internal.h" + +/* Our GWIN identifier */ +#define GW_SCOPE (GW_FIRST_USER_WINDOW+0) + +/* The size of our dynamically allocated audio buffer */ +#define AUDIOBUFSZ 64*2 + +/* How many flat-line sample before we trigger */ +#define FLATLINE_SAMPLES 8 + +GHandle gwinCreateScope(GScopeObject *gs, coord_t x, coord_t y, coord_t cx, coord_t cy, uint32_t physdev, uint32_t frequency) { + /* Initialise the base class GWIN */ + if (!(gs = (GScopeObject *)_gwinInit((GWindowObject *)gs, x, y, cx, cy, sizeof(GScopeObject)))) + return 0; + + /* Initialise the scope object members and allocate memory for buffers */ + gs->gwin.type = GW_SCOPE; + chBSemInit(&gs->bsem, TRUE); + gs->nextx = 0; + if (!(gs->lastscopetrace = (coord_t *)chHeapAlloc(NULL, gs->gwin.width * sizeof(coord_t)))) + return 0; + if (!(gs->audiobuf = (adcsample_t *)chHeapAlloc(NULL, AUDIOBUFSZ * sizeof(adcsample_t)))) + return 0; +#if TRIGGER_METHOD == TRIGGER_POSITIVERAMP + gs->lasty = gs->gwin.height/2; +#elif TRIGGER_METHOD == TRIGGER_MINVALUE + gs->lasty = gs->gwin.height/2; + gs->scopemin = 0; +#endif + + /* Start the GADC high speed converter */ + gadcHighSpeedInit(physdev, frequency, gs->audiobuf, AUDIOBUFSZ, AUDIOBUFSZ/2); + gadcHighSpeedSetBSem(&gs->bsem, &gs->myEvent); + gadcHighSpeedStart(); + + return (GHandle)gs; +} + +void gwinWaitForScopeTrace(GHandle gh) { + #define gs ((GScopeObject *)(gh)) + int i; + coord_t x, y; + coord_t yoffset; + adcsample_t *pa; + coord_t *pc; +#if TRIGGER_METHOD == TRIGGER_POSITIVERAMP + bool_t rdytrigger; + int flsamples; +#elif TRIGGER_METHOD == TRIGGER_MINVALUE + bool_t rdytrigger; + int flsamples; + coord_t scopemin; +#endif + + /* Wait for a set of audio conversions */ + chBSemWait(&gs->bsem); + + /* Ensure we are drawing in the right area */ + #if GDISP_NEED_CLIP + gdispSetClip(gh->x, gh->y, gh->width, gh->height); + #endif + + yoffset = gh->height/2 + (1<nextx; + pc = gs->lastscopetrace+x; + pa = gs->myEvent.buffer; +#if TRIGGER_METHOD == TRIGGER_POSITIVERAMP + rdytrigger = FALSE; + flsamples = 0; +#elif TRIGGER_METHOD == TRIGGER_MINVALUE + rdytrigger = FALSE; + flsamples = 0; + scopemin = 0; +#endif + + for(i = gs->myEvent.count; i; i--) { + + /* Calculate the new scope value - re-scale using simple shifts for efficiency, re-center and y-invert */ + #if GADC_BITS_PER_SAMPLE > SCOPE_Y_BITS + y = yoffset - (*pa++ >> (GADC_BITS_PER_SAMPLE - SCOPE_Y_BITS)); + #else + y = yoffset - (*pa++ << (SCOPE_Y_BITS - GADC_BITS_PER_SAMPLE)); + #endif + +#if TRIGGER_METHOD == TRIGGER_MINVALUE + /* Calculate the scopemin ready for the next trace */ + if (y > scopemin) + scopemin = y; +#endif + + /* Have we reached the end of a scope trace? */ + if (x >= gh->width) { + +#if TRIGGER_METHOD == TRIGGER_POSITIVERAMP || TRIGGER_METHOD == TRIGGER_MINVALUE + /* Handle triggering - we trigger on the next sample minimum (y value maximum) or a flat-line */ + + #if TRIGGER_METHOD == TRIGGER_MINVALUE + /* Arm when we reach the sample minimum (y value maximum) of the previous trace */ + if (!rdytrigger && y >= gs->scopemin) + rdytrigger = TRUE; + #endif + + if (y == gs->lasty) { + /* Trigger if we get too many flat-line samples regardless of the armed state */ + if (++flsamples < FLATLINE_SAMPLES) + continue; + flsamples = 0; + } else if (y > gs->lasty) { + gs->lasty = y; + flsamples = 0; + #if TRIGGER_METHOD == TRIGGER_POSITIVERAMP + /* Arm the trigger when samples fall (y increases) ie. negative slope */ + rdytrigger = TRUE; + #endif + continue; + } else { + /* If the trigger is armed, Trigger when samples increases (y decreases) ie. positive slope */ + gs->lasty = y; + flsamples = 0; + if (!rdytrigger) + continue; + } + + /* Ready for a the next trigger cycle */ + rdytrigger = FALSE; +#endif + + /* Prepare for a scope trace */ + x = 0; + pc = gs->lastscopetrace; + } + + /* Clear the old scope pixel and then draw the new scope value */ + gdispDrawPixel(gh->x+x, gh->y+pc[0], gh->bgcolor); + gdispDrawPixel(gh->x+x, gh->y+y, gh->color); + + /* Save the value */ + *pc++ = y; + x++; + #if TRIGGER_METHOD == TRIGGER_POSITIVERAMP || TRIGGER_METHOD == TRIGGER_MINVALUE + gs->lasty = y; + #endif + } + gs->nextx = x; +#if TRIGGER_METHOD == TRIGGER_MINVALUE + gs->scopemin = scopemin; +#endif + + #undef gs +} diff --git a/demos/modules/gadc/gwinosc.h b/demos/modules/gadc/gwinosc.h new file mode 100644 index 00000000..ec44937d --- /dev/null +++ b/demos/modules/gadc/gwinosc.h @@ -0,0 +1,89 @@ +/* + ChibiOS/GFX - Copyright (C) 2012, 2013 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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. + + ChibiOS/GFX 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, see . +*/ + +#ifndef _GWINOSC_H +#define _GWINOSC_H + +/** + * --------------------------- Our Custom GWIN Oscilloscope --------------- + * + * This GWIN superset implements a simple audio oscilloscope using the GADC high speed device. + */ + +/* The extent of scaling for our audio data - fixed scale at the moment */ +#ifndef SCOPE_Y_BITS + #define SCOPE_Y_BITS 7 // 7 bits = 0..128 +#endif + +/* Trigger methods */ +#define TRIGGER_NONE 0 /* No triggering */ +#define TRIGGER_POSITIVERAMP 1 /* Trigger on a positive going signal */ +#define TRIGGER_MINVALUE 2 /* Trigger on reaching the minimum value from the last scope */ + +/** + * Which trigger we want to use. + * Experiments suggests that TRIGGER_MINVALUE gives the best result + */ +#ifndef TRIGGER_METHOD + #define TRIGGER_METHOD TRIGGER_MINVALUE +#endif + +/* A scope window object. Treat it as a black box */ +typedef struct GScopeObject_t { + GWindowObject gwin; // Base Class + + coord_t *lastscopetrace; // To store last scope trace + BinarySemaphore bsem; // We get signalled on this + adcsample_t *audiobuf; // To store audio samples + GEventADC myEvent; // Information on received samples + coord_t nextx; // Where we are up to +#if TRIGGER_METHOD == TRIGGER_POSITIVERAMP + coord_t lasty; // The last y value - used for trigger slope detection +#elif TRIGGER_METHOD == TRIGGER_MINVALUE + coord_t lasty; // The last y value - used for trigger slope detection + coord_t scopemin; // The last scopes minimum value +#endif + } GScopeObject; + +#ifdef __cplusplus +extern "C" { +#endif + + /** + * Create a scope window. + */ + GHandle gwinCreateScope(GScopeObject *gs, coord_t x, coord_t y, coord_t cx, coord_t cy, uint32_t physdev, uint32_t frequency); + + /** + * Wait for a scope trace to be ready and then draw it. + */ + void gwinWaitForScopeTrace(GHandle gh); + + /** + * We should also have a special destroy routine here as we have dynamically + * allocated some memory. There is no point implementing this however as, for + * this demo, we never destroy the window. + */ + +#ifdef __cplusplus +} +#endif + +#endif /* _GWINOSC_H */ diff --git a/demos/modules/gadc/main.c b/demos/modules/gadc/main.c new file mode 100644 index 00000000..07802521 --- /dev/null +++ b/demos/modules/gadc/main.c @@ -0,0 +1,176 @@ +/* + ChibiOS/GFX - Copyright (C) 2012, 2013 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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. + + ChibiOS/GFX 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, see . +*/ + +/** + * This demo demonstrates the use of the GADC module using it read both a microphone, + * an analogue dial wheel and a temperature sensor. + * The microphone gets read at high frequency to display a very simple oscilloscope. + * The dial and temperature gets read at a low frequency to just print when + * it changes value. + * + * It also demonstrates how to write your own custom GWIN window type. + */ +#include "ch.h" +#include "hal.h" +#include "chprintf.h" + +#include "gfx.h" + +/* Include our custom gwin oscilloscope */ +#include "gwinosc.h" + +/* + * Match these to your hardware + * If you don't have a DIAL device or a TEMP device - just don't define it. + */ +#define MY_MIC_DEVICE GADC_PHYSDEV_MICROPHONE +#define MY_DIAL_DEVICE GADC_PHYSDEV_DIAL +#define MY_TEMP_DEVICE GADC_PHYSDEV_TEMPERATURE +#define MY_DIAL_JITTER 1 +#define MY_TEMP_JITTER 3 + +/* Specify our timing parameters */ +#define MY_MIC_FREQUENCY 4000 /* 4khz */ +#define MY_LS_DELAY 200 /* 200ms (5 times per second) for the dial and temperature */ + +/* The desired size for our scope window */ +#define SCOPE_CX 64 +#define SCOPE_CY 64 + +/* Data */ +static GScopeObject gScopeWindow; +static GConsoleObject gTextWindow; +static GTimer lsTimer; + +#ifdef MY_DIAL_DEVICE + static adcsample_t dialvalue; + static adcsample_t lastdial = -(MY_DIAL_JITTER+1); + + /** + * We have got a dial reading - handle it + */ + static void GotDialReading(adcsample_t *buffer, void *param) { + (void) buffer; + + /* Buffer should always point to "dialvalue" anyway */ + + /* Remove jitter from the value */ + if ((dialvalue > lastdial && dialvalue - lastdial > MY_DIAL_JITTER) + || (lastdial > dialvalue && lastdial - dialvalue > MY_DIAL_JITTER)) { + + /* Write the value */ + chprintf((BaseSequentialStream *)param, "DIAL: %u\n", dialvalue); + + /* Save for next time */ + lastdial = dialvalue; + } + } +#endif + +#ifdef MY_TEMP_DEVICE + static adcsample_t tempvalue; + static adcsample_t lasttemp = -(MY_TEMP_JITTER+1); + + /** + * We have got a temperature reading - handle it + */ + static void GotTempReading(adcsample_t *buffer, void *param) { + (void) buffer; + + /* Buffer should always point to "tempvalue" anyway */ + + /* Remove jitter from the value */ + if ((tempvalue > lasttemp && tempvalue - lasttemp > MY_TEMP_JITTER) + || (lasttemp > tempvalue && lasttemp - tempvalue > MY_TEMP_JITTER)) { + + /* Write the value */ + chprintf((BaseSequentialStream *)param, "TEMP: %u\n", tempvalue); + + /* Save for next time */ + lasttemp = tempvalue; + } + } +#endif + +#if defined(MY_DIAL_DEVICE) || defined(MY_TEMP_DEVICE) + /** + * Start a read of the dial and temperature + */ + static void LowSpeedTimer(void *param) { + /* We are not checking for an error here - but who cares, this is just a demo */ + #ifdef MY_DIAL_DEVICE + gadcLowSpeedStart(MY_DIAL_DEVICE, &dialvalue, GotDialReading, param); + #endif + #ifdef MY_TEMP_DEVICE + gadcLowSpeedStart(MY_TEMP_DEVICE, &tempvalue, GotTempReading, param); + #endif + } +#endif + +/* + * Application entry point. + */ +int main(void) { + GHandle ghScope; + coord_t swidth, sheight; + #if defined(MY_DIAL_DEVICE) || defined(MY_TEMP_DEVICE) + GHandle ghText; + BaseSequentialStream *gsText; + font_t font; + #endif + + halInit(); + chSysInit(); + gdispInit(); + gdispClear(Black); + + /* Get the screen dimensions */ + swidth = gdispGetWidth(); + sheight = gdispGetHeight(); + + #if defined(MY_DIAL_DEVICE) || defined(MY_TEMP_DEVICE) + /* Set up the console window we use for dial readings */ + font = gdispOpenFont("UI2"); + ghText = gwinCreateConsole(&gTextWindow, 0, 0, swidth-SCOPE_CX, sheight, font); + gwinSetBgColor(ghText, Black); + gwinSetColor(ghText, Yellow); + gwinClear(ghText); + gsText = gwinGetConsoleStream(ghText); + + /* Start our timer for reading the dial */ + gtimerInit(&lsTimer); + gtimerStart(&lsTimer, LowSpeedTimer, gsText, TRUE, MY_LS_DELAY); + #endif + + /* Set up the scope window in the top right on the screen */ + ghScope = gwinCreateScope(&gScopeWindow, swidth-SCOPE_CX, 0, SCOPE_CX, SCOPE_CY, MY_MIC_DEVICE, MY_MIC_FREQUENCY); + gwinSetBgColor(ghScope, White); + gwinSetColor(ghScope, Red); + gwinClear(ghScope); + + /* Just keep displaying the scope traces */ + while (TRUE) { + /** + * The function below internally performs a wait thus giving the timer thread a + * chance to run. + */ + gwinWaitForScopeTrace(ghScope); + } +} diff --git a/demos/modules/gadc/results_264x264.jpg b/demos/modules/gadc/results_264x264.jpg new file mode 100644 index 0000000000000000000000000000000000000000..25aadf81f986b288f39a12a1433ffbacfc9fdbb4 GIT binary patch literal 21130 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 + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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. + + ChibiOS/GFX 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, see . +*/ +/** + * @file include/gadc/lld/gadc_lld.c + * @brief GADC - Periodic ADC driver source file for the AT91SAM7 cpu. + * + * @defgroup Driver Driver + * @ingroup GADC + * @{ + */ + +#include "ch.h" +#include "hal.h" +#include "gfx.h" + +#if GFX_USE_GADC + +#include "gadc/lld/gadc_lld.h" + +static ADCConversionGroup acg = { + FALSE, // circular + 1, // num_channels + GADC_ISR_CompleteI, // end_cb + GADC_ISR_ErrorI, // error_cb + 0, // channelselects + 0, // trigger + 0, // frequency + }; + +void gadc_lld_init(void) { + adcStart(&ADCD1, NULL); +} + +size_t gadc_lld_samples_per_conversion(uint32_t physdev) { + size_t cnt; + int i; + + /* The AT91SAM7 has AD0..7 - physdev is a bitmap of those channels */ + for(cnt = 0, i = 0; i < 8; i++, physdev >>= 1) + if (physdev & 0x01) + cnt++; + return cnt; +} + +void gadc_lld_start_timer(uint32_t physdev, uint32_t frequency) { + (void) physdev; + /** + * 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 = frequency; +} + +void gadc_lld_stop_timer(uint32_t physdev) { + (void) physdev; + if ((acg.trigger & ~ADC_TRIGGER_SOFTWARE) == ADC_TRIGGER_TIMER) + adcStop(&ADCD1); +} + +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; + + adcStartConversionI(&ADCD1, &acg, pgtd->buffer, pgtd->count); + + /* Next time assume the same (still running) timer */ + acg.frequency = 0; +} + +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; + acg.trigger = ADC_TRIGGER_SOFTWARE; + adcStartConversionI(&ADCD1, &acg, pgntd->buffer, 1); +} + +#endif /* GFX_USE_GADC */ +/** @} */ diff --git a/drivers/gadc/AT91SAM7/gadc_lld.mk b/drivers/gadc/AT91SAM7/gadc_lld.mk new file mode 100644 index 00000000..001d44b1 --- /dev/null +++ b/drivers/gadc/AT91SAM7/gadc_lld.mk @@ -0,0 +1,5 @@ +# List the required driver. +GFXSRC += $(GFXLIB)/drivers/gadc/AT91SAM7/gadc_lld.c + +# Required include directories +GFXINC += $(GFXLIB)/drivers/gadc/AT91SAM7 diff --git a/drivers/gadc/AT91SAM7/gadc_lld_board_olimexsam7ex256.h b/drivers/gadc/AT91SAM7/gadc_lld_board_olimexsam7ex256.h new file mode 100644 index 00000000..6f23db17 --- /dev/null +++ b/drivers/gadc/AT91SAM7/gadc_lld_board_olimexsam7ex256.h @@ -0,0 +1,46 @@ +/* + ChibiOS/GFX - Copyright (C) 2012, 2013 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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. + + ChibiOS/GFX 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, see . +*/ + +/** + * @file drivers/gadc/AT91SAM7/gadc_lld_board_olimexsam7ex256.h + * @brief GADC Driver config file. + * + * @addtogroup GADC + * @{ + */ + +#ifndef _GADC_LLD_BOARD_OLIMEXSAM7EX256_H +#define _GADC_LLD_BOARD_OLIMEXSAM7EX256_H + +#if GFX_USE_GADC + +/*===========================================================================*/ +/* Analogue devices on this board */ +/*===========================================================================*/ + +#define GADC_PHYSDEV_MICROPHONE 0x00000080 +#define GADC_PHYSDEV_DIAL 0x00000040 +#define GADC_PHYSDEV_TEMPERATURE 0x00000020 + +#endif /* GFX_USE_GADC */ + +#endif /* _GADC_LLD_BOARD_OLIMEXSAM7EX256_H */ +/** @} */ + diff --git a/drivers/gadc/AT91SAM7/gadc_lld_config.h b/drivers/gadc/AT91SAM7/gadc_lld_config.h new file mode 100644 index 00000000..882573c8 --- /dev/null +++ b/drivers/gadc/AT91SAM7/gadc_lld_config.h @@ -0,0 +1,78 @@ +/* + ChibiOS/GFX - Copyright (C) 2012, 2013 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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. + + ChibiOS/GFX 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, see . +*/ + +/** + * @file drivers/gadc/AT91SAM7/gadc_lld_config.h + * @brief GADC Driver config file. + * + * @addtogroup GADC + * @{ + */ + +#ifndef GADC_LLD_CONFIG_H +#define GADC_LLD_CONFIG_H + +#if GFX_USE_GADC + +/*===========================================================================*/ +/* Driver hardware support. */ +/*===========================================================================*/ + +/** + * @brief ChibiOS has a nasty bug in its _adc_isr_full_code() routine (defined in adc.h as a macro). + * Do we have the version of ChibiOS with this bug. + * @detail Set to TRUE if ChibiOS has this bug. + * @note Fixed in ChibiOS 2.4.4stable and 2.5.2unstable (and the repository from 18th Feb 2013) + * @note This bug prevents us re-calling adcStartConversionI() from with the ISR even though + * it is clearly designed to handle it. For some reason (on this micro) the high speed timer + * is not affected only the single sample low speed timer. In that situation we wait until + * we get back to thread land. This is terrible for the accuracy of the high speed timer + * but what can we do (other than fix the bug). + * @note For the AT91SAM7 ADC driver, it post-dates the finding of the bug so we safely + * say that the bug doesn't exist for this driver. + */ +#define ADC_ISR_FULL_CODE_BUG FALSE + +/** + * @brief The maximum sample frequency supported by this CPU + */ +#define GADC_MAX_SAMPLE_FREQUENCY 132000 + +/** + * @brief The number of bits in a sample + */ +#define GADC_BITS_PER_SAMPLE AT91_ADC1_RESOLUTION + +/* Pull in board specific defines */ +#if defined(GADC_USE_CUSTOM_BOARD) && GADC_USE_CUSTOM_BOARD + /* Include the user supplied board definitions */ + #include "gadc_lld_board.h" +#elif defined(BOARD_OLIMEX_SAM7_EX256) + #include "gadc_lld_board_olimexsam7ex256.h" +#else + /* Include the user supplied board definitions */ + #include "gadc_lld_board.h" +#endif + +#endif /* GFX_USE_GADC */ + +#endif /* _GDISP_LLD_CONFIG_H */ +/** @} */ + diff --git a/gfxconf.example.h b/gfxconf.example.h index c601dbb9..389d4db5 100644 --- a/gfxconf.example.h +++ b/gfxconf.example.h @@ -96,10 +96,8 @@ #define GDISP_SCREEN_HEIGHT 240 #define GDISP_USE_FSMC #define GDISP_USE_GPIO - #define TDISP_COLUMNS 16 #define TDISP_ROWS 2 */ #endif /* _GFXCONF_H */ - diff --git a/include/gadc/gadc.h b/include/gadc/gadc.h index 5c490cb9..be7af516 100644 --- a/include/gadc/gadc.h +++ b/include/gadc/gadc.h @@ -1,250 +1,258 @@ -/* - ChibiOS/GFX - Copyright (C) 2012 - Joel Bodenmann aka Tectu - - This file is part of ChibiOS/GFX. - - ChibiOS/GFX 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. - - ChibiOS/GFX 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, see . -*/ -/** - * @file include/gadc/gadc.h - * @brief GADC - Periodic ADC subsystem header file. - * - * @addtogroup GADC - * - * @details The reason why ChibiOS/GFX has it's own ADC abstraction is because - * the Chibi-OS drivers are very CPU specific and do not - * provide a way across all hardware platforms to create periodic - * ADC conversions. There are also issues with devices with different - * characteristics or periodic requirements on the same ADC - * device (but different channels). This layer attempts to solve these - * problems to provide a architecture neutral API. It also provides extra - * features such as multi-buffer chaining for high speed ADC sources. - * It provides one high speed virtual ADC device (eg a microphone) and - * numerous low speed (less than 100Hz) virtual ADC devices (eg dials, - * temperature sensors etc). The high speed device has timer based polling - * to ensure exact conversion periods and a buffer management system. - * The low speed devices are assumed to be non-critical timing devices - * and do not have any buffer management. - * Note that while only one high speed device has been provided it can - * be used to read multiple physical ADC channels on the one physical - * ADC device. - * All callback routines are thread based unlike the Chibi-OS interrupt based - * routines. - * - * @{ - */ - -#ifndef _GADC_H -#define _GADC_H - -#include "gfx.h" - -#if GFX_USE_GADC || defined(__DOXYGEN__) - -/* Include the driver defines */ -#include "gadc_lld_config.h" - -/*===========================================================================*/ -/* Type definitions */ -/*===========================================================================*/ - -// Event types for GADC -#define GEVENT_ADC (GEVENT_GADC_FIRST+0) - -/** - * @brief The High Speed ADC event structure. - * @{ - */ -typedef struct GEventADC_t { - /** - * @brief The type of this event (GEVENT_ADC) - */ - GEventType type; - /** - * @brief The event flags - */ - uint16_t flags; - /** - * @brief The event flag values. - * @{ - */ - #define GADC_HSADC_LOSTEVENT 0x0001 /**< @brief The last GEVENT_HSDADC event was lost */ - /** @} */ - /** - * @brief The number of conversions in the buffer - */ - size_t count; - /** - * @brief The buffer containing the conversion samples - */ - adcsample_t *buffer; - } GEventADC; - -/** - * @brief A callback function (executed in a thread context) - */ -typedef void (*GADCCallbackFunction)(adcsample_t *buffer, void *param); - -/*===========================================================================*/ -/* External declarations. */ -/*===========================================================================*/ - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Initialise the high speed ADC. - * @details Initialises but does not start the conversions. - * - * @param[in] physdev A value passed to describe which physical ADC devices/channels to use. - * @param[in] frequency The frequency to create ADC conversions - * @param[in] buffer The static buffer to put the ADC samples into. - * @param[in] bufcount The total number of conversions that will fit in the buffer. - * @param[in] countPerEvent The number of conversions to do before returning an event. - * - * @note If the high speed ADC is running it will be stopped. - * @note Due to a bug in Chibi-OS countPerEvent must be even. If bufcount is not - * evenly divisable by countPerEvent, the remainder must also be even. - * @note The physdev parameter may be used to turn on more than one ADC channel. - * Each channel is then interleaved into the provided buffer. Note 'bufcount' - * and 'countPerEvent' parameters describe the number of conversions not the - * number of samples. - * As an example, if physdev turns on 2 devices then the buffer contains - * alternate device samples and the buffer must contain 2 * bufcount samples. - * The exact meaning of physdev is hardware dependent. - * @note The buffer is circular. When the end of the buffer is reached it will start - * putting data into the beginning of the buffer again. - * @note The event listener must process the event (and the data in it) before the - * next event occurs. If not, the following event will be lost. - * @note If bufcount is evenly divisable by countPerEvent, then every event will return - * countPerEvent conversions. If bufcount is not evenly divisable, it will return - * a block of samples containing less than countPerEvent samples when it reaches the - * end of the buffer. - * @note While the high speed ADC is running, low speed conversions can only occur at - * the frequency of the high speed events. Thus if high speed events are - * being created at 50Hz (eg countPerEvent = 100, frequency = 5kHz) then the maximum - * frequency for low speed conversions is likely to be 50Hz (although it might be - * 100Hz on some hardware). - * - * @api - */ -void gadcHighSpeedInit(uint32_t physdev, uint32_t frequency, adcsample_t *buffer, size_t bufcount, size_t samplesPerEvent); - -#if GFX_USE_GEVENT || defined(__DOXYGEN__) - /** - * @brief Turn on sending results to the GEVENT sub-system. - * @details Returns a GSourceHandle to listen for GEVENT_ADC events. - * - * @note The high speed ADC will not use the GEVENT system unless this is - * called first. This saves processing time if the application does - * not want to use the GEVENT sub-system for the high speed ADC. - * Once turned on it cannot be turned off. - * @note The high speed ADC is capable of signalling via this method and a binary semaphore - * at the same time. - * - * @api - */ - GSourceHandle gadcHighSpeedGetSource(void); -#endif - -/** - * @brief Allow retrieving of results from the high speed ADC using a Binary Semaphore and a static event buffer. - * - * @param[in] pbsem The binary semaphore is signaled when data is available. - * @param[in] pEvent The static event buffer to place the result information. - * - * @note Passing a NULL for pbsem or pEvent will turn off signalling via this method. - * @note The high speed ADC is capable of signalling via this method and the GEVENT - * sub-system at the same time. - * - * @api - */ -void gadcHighSpeedSetBSem(BinarySemaphore *pbsem, GEventADC *pEvent); - -/** - * @brief Start the high speed ADC conversions. - * @pre It must have been initialised first with @p gadcHighSpeedInit() - * - * @api - */ -GSourceHandle gadcHighSpeedStart(void); - -/** - * @brief Stop the high speed ADC conversions. - * - * @api - */ -void gadcHighSpeedStop(void); - -/** - * @brief Perform a single low speed ADC conversion - * @details Blocks until the conversion is complete - * @pre This should not be called from within a GTimer callback as this routine - * blocks until the conversion is ready. - * - * @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. - * - * @note This may take a while to complete if the high speed ADC is running as the - * conversion is interleaved with the high speed ADC conversions on a buffer - * 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. - * - * @api - */ -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. - * - * @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. - * @param[in] fn The callback function to call when the conversion is complete. - * @param[in] param A parameter to pass to the callback function. - * - * @note This may be safely called from within a GTimer callback. - * @note The callback may take a while to occur if the high speed ADC is running as the - * conversion is interleaved with the high speed ADC conversions on a 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. - * - * @api - */ -bool gadcLowSpeedStart(uint32_t physdev, adcsample_t *buffer, GADCCallbackFunction fn, void *param); - -#ifdef __cplusplus -} -#endif - -#endif /* GFX_USE_GADC */ - -#endif /* _GADC_H */ -/** @} */ - +/* + ChibiOS/GFX - Copyright (C) 2012, 2013 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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. + + ChibiOS/GFX 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, see . +*/ +/** + * @file include/gadc/gadc.h + * @brief GADC - Periodic ADC subsystem header file. + * + * @addtogroup GADC + * + * @details The reason why ChibiOS/GFX has it's own ADC abstraction is because + * the Chibi-OS drivers are very CPU specific and do not + * provide a way across all hardware platforms to create periodic + * ADC conversions. There are also issues with devices with different + * characteristics or periodic requirements on the same ADC + * device (but different channels). This layer attempts to solve these + * problems to provide a architecture neutral API. It also provides extra + * features such as multi-buffer chaining for high speed ADC sources. + * It provides one high speed virtual ADC device (eg a microphone) and + * numerous low speed (less than 100Hz) virtual ADC devices (eg dials, + * temperature sensors etc). The high speed device has timer based polling + * to ensure exact conversion periods and a buffer management system. + * The low speed devices are assumed to be non-critical timing devices + * and do not have any buffer management. + * Note that while only one high speed device has been provided it can + * be used to read multiple physical ADC channels on the one physical + * ADC device. + * All callback routines are thread based unlike the Chibi-OS interrupt based + * routines. + * + * @{ + */ + +#ifndef _GADC_H +#define _GADC_H + +#include "gfx.h" + +#if GFX_USE_GADC || defined(__DOXYGEN__) + +/* Include the driver defines */ +#include "gadc_lld_config.h" + +/*===========================================================================*/ +/* Type definitions */ +/*===========================================================================*/ + +// Event types for GADC +#define GEVENT_ADC (GEVENT_GADC_FIRST+0) + +/** + * @brief The High Speed ADC event structure. + * @{ + */ +typedef struct GEventADC_t { + #if GFX_USE_GEVENT || defined(__DOXYGEN__) + /** + * @brief The type of this event (GEVENT_ADC) + */ + GEventType type; + #endif + + /** + * @brief The event flags + */ + uint16_t flags; + /** + * @brief The event flag values. + * @{ + */ + #define GADC_HSADC_LOSTEVENT 0x0001 /**< @brief The last GEVENT_HSDADC event was lost */ + /** @} */ + /** + * @brief The number of conversions in the buffer + */ + size_t count; + /** + * @brief The buffer containing the conversion samples + */ + adcsample_t *buffer; + } GEventADC; + +/** + * @brief A callback function (executed in a thread context) + */ +typedef void (*GADCCallbackFunction)(adcsample_t *buffer, void *param); + +/*===========================================================================*/ +/* External declarations. */ +/*===========================================================================*/ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialise the high speed ADC. + * @details Initialises but does not start the conversions. + * + * @param[in] physdev A value passed to describe which physical ADC devices/channels to use. + * @param[in] frequency The frequency to create ADC conversions + * @param[in] buffer The static buffer to put the ADC samples into. + * @param[in] bufcount The total number of conversions that will fit in the buffer. + * @param[in] countPerEvent The number of conversions to do before returning an event. + * + * @note If the high speed ADC is running it will be stopped. The Event subsystem is + * disconnected from the high speed ADC and any binary semaphore event is forgotten. + * @note bufcount must be greater than countPerEvent (usually 2 or more times) otherwise + * the buffer will be overwitten with new data while the application is still trying + * to process the old data. + * @note Due to a bug in Chibi-OS countPerEvent must be even. If bufcount is not + * evenly divisable by countPerEvent, the remainder must also be even. + * @note The physdev parameter may be used to turn on more than one ADC channel. + * Each channel is then interleaved into the provided buffer. Note 'bufcount' + * and 'countPerEvent' parameters describe the number of conversions not the + * number of samples. + * As an example, if physdev turns on 2 devices then the buffer contains + * alternate device samples and the buffer must contain 2 * bufcount samples. + * The exact meaning of physdev is hardware dependent. + * @note The buffer is circular. When the end of the buffer is reached it will start + * putting data into the beginning of the buffer again. + * @note The event listener must process the event (and the data in it) before the + * next event occurs. If not, the following event will be lost. + * @note If bufcount is evenly divisable by countPerEvent, then every event will return + * countPerEvent conversions. If bufcount is not evenly divisable, it will return + * a block of samples containing less than countPerEvent samples when it reaches the + * end of the buffer. + * @note While the high speed ADC is running, low speed conversions can only occur at + * the frequency of the high speed events. Thus if high speed events are + * being created at 50Hz (eg countPerEvent = 100, frequency = 5kHz) then the maximum + * frequency for low speed conversions is likely to be 50Hz (although it might be + * 100Hz on some hardware). + * + * @api + */ +void gadcHighSpeedInit(uint32_t physdev, uint32_t frequency, adcsample_t *buffer, size_t bufcount, size_t samplesPerEvent); + +#if GFX_USE_GEVENT || defined(__DOXYGEN__) + /** + * @brief Turn on sending results to the GEVENT sub-system. + * @details Returns a GSourceHandle to listen for GEVENT_ADC events. + * + * @note The high speed ADC will not use the GEVENT system unless this is + * called first. This saves processing time if the application does + * not want to use the GEVENT sub-system for the high speed ADC. + * Once turned on it can only be turned off by calling @p gadcHighSpeedInit() again. + * @note The high speed ADC is capable of signalling via this method and a binary semaphore + * at the same time. + * + * @api + */ + GSourceHandle gadcHighSpeedGetSource(void); +#endif + +/** + * @brief Allow retrieving of results from the high speed ADC using a Binary Semaphore and a static event buffer. + * + * @param[in] pbsem The binary semaphore is signaled when data is available. + * @param[in] pEvent The static event buffer to place the result information. + * + * @note Passing a NULL for pbsem or pEvent will turn off signalling via this method as will calling + * @p gadcHighSpeedInit(). + * @note The high speed ADC is capable of signalling via this method and the GEVENT + * sub-system at the same time. + * + * @api + */ +void gadcHighSpeedSetBSem(BinarySemaphore *pbsem, GEventADC *pEvent); + +/** + * @brief Start the high speed ADC conversions. + * @pre It must have been initialised first with @p gadcHighSpeedInit() + * + * @api + */ +void gadcHighSpeedStart(void); + +/** + * @brief Stop the high speed ADC conversions. + * + * @api + */ +void gadcHighSpeedStop(void); + +/** + * @brief Perform a single low speed ADC conversion + * @details Blocks until the conversion is complete + * @pre This should not be called from within a GTimer callback as this routine + * blocks until the conversion is ready. + * + * @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. + * + * @note This may take a while to complete if the high speed ADC is running as the + * conversion is interleaved with the high speed ADC conversions on a buffer + * 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. + * + * @api + */ +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. + * + * @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. + * @param[in] fn The callback function to call when the conversion is complete. + * @param[in] param A parameter to pass to the callback function. + * + * @note This may be safely called from within a GTimer callback. + * @note The callback may take a while to occur if the high speed ADC is running as the + * conversion is interleaved with the high speed ADC conversions on a 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. + * + * @api + */ +bool_t gadcLowSpeedStart(uint32_t physdev, adcsample_t *buffer, GADCCallbackFunction fn, void *param); + +#ifdef __cplusplus +} +#endif + +#endif /* GFX_USE_GADC */ + +#endif /* _GADC_H */ +/** @} */ + diff --git a/include/gadc/lld/gadc_lld.h b/include/gadc/lld/gadc_lld.h new file mode 100644 index 00000000..f9cc8b47 --- /dev/null +++ b/include/gadc/lld/gadc_lld.h @@ -0,0 +1,190 @@ +/* + ChibiOS/GFX - Copyright (C) 2012, 2013 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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. + + ChibiOS/GFX 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, see . +*/ +/** + * @file include/gadc/lld/gadc_lld.h + * @brief GADC - Periodic ADC driver header file. + * + * @defgroup Driver Driver + * @ingroup GADC + * @{ + */ + +#ifndef _GADC_LLD_H +#define _GADC_LLD_H + +#include "gfx.h" + +#if GFX_USE_GADC || defined(__DOXYGEN__) + +/*===========================================================================*/ +/* Type definitions */ +/*===========================================================================*/ + +/** + * @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; /* @< A value passed to describe which physical ADC devices/channels to use. */ + adcsample_t *buffer; /* @< The static buffer to put the ADC samples into. */ + size_t count; /* @< The number of conversions to do before doing a callback and stopping the ADC. */ + bool_t now; /* @< Trigger the first conversion now rather than waiting for the first timer interrupt (if possible) */ + } GadcLldTimerData; +/* @} */ + +/** + * @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. + * @{ + */ +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; +/* @} */ + +/** + * @brief These routines are the callbacks that the driver uses. + * @details Defined in the high level GADC code. + * + * @notapi + * @{ + */ +extern void GADC_ISR_CompleteI(ADCDriver *adcp, adcsample_t *buffer, size_t n); +extern void GADC_ISR_ErrorI(ADCDriver *adcp, adcerror_t err); +/** + * @} + */ + +/** + * @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. */ +/*===========================================================================*/ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialise the driver + * + * @api + */ +void gadc_lld_init(void); + +/** + * @brief Get the number of samples in a conversion. + * @details Calculates and returns the number of samples per conversion for the specified physdev. + * + * @param[in] physdev A value passed to describe which physical ADC devices/channels to use. + * + * @note A physdev describing a mono device would return 1, a stereo device would return 2. + * For most ADC's physdev is a bitmap so it is only a matter of counting the bits. + * + * @api + */ +size_t gadc_lld_samples_per_conversion(uint32_t physdev); + +/** + * @brief Start a periodic timer for high frequency conversions. + * + * @param[in] physdev A value passed to describe which physical ADC devices/channels to use. + * @param[in] frequency The frequency to create ADC conversions + * + * @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 these is a "timer" conversion + * active. + * @note If the ADC is stopped, doesn't have a "timer" conversion active or is currently executing + * a non-timer conversion then the interrupt can be ignored other than (optionally) incrementing + * the GADC_Timer_Missed variable. + * + * @api + */ +void gadc_lld_start_timer(uint32_t physdev, uint32_t frequency); + +/** + * @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] physdev A value passed to describe which physical ADC devices/channels in use. + * + * @note The exact meaning of physdev is hardware dependent. + * + * @api + */ +void gadc_lld_stop_timer(uint32_t physdev); + +/** + * @brief Start a "timer" conversion. + * @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 Some versions of ChibiOS actually call the callback function more than once, once + * at the half-way point and once on completion. The high level code handles this. + * @note The driver should call @p GADC_ISR_CompleteI() when it completes the operation + * (or at the half-way point), or @p GAD_ISR_ErrorI() 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); + +/** + * @brief Start a "non-timer" conversion. + * @details Starts a single conversion now. + * + * @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 GADC_ISR_CompleteI() when it completes the operation + * or @p GAD_ISR_ErrorI() on an error. + * @note The high level code ensures that this is not called while a timer conversion is in + * progress + * + * @iclass + */ +void gadc_lld_adc_nontimerI(GadcLldNonTimerData *pgntd); + +#ifdef __cplusplus +} +#endif + +#endif /* GFX_USE_GADC */ + +#endif /* _GADC_LLD_H */ +/** @} */ diff --git a/include/gadc/options.h b/include/gadc/options.h index dc5bd300..87708efe 100644 --- a/include/gadc/options.h +++ b/include/gadc/options.h @@ -1,57 +1,55 @@ -/* - ChibiOS/GFX - Copyright (C) 2012 - Joel Bodenmann aka Tectu - - This file is part of ChibiOS/GFX. - - ChibiOS/GFX 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. - - ChibiOS/GFX 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, see . -*/ - -/** - * @file include/gadc/options.h - * @brief GADC - Periodic ADC subsystem options header file. - * - * @addtogroup GADC - * @{ - */ - -#ifndef _GADC_OPTIONS_H -#define _GADC_OPTIONS_H - -/** - * @name GADC Functionality to be included - * @{ - */ -/** - * @} - * - * @name GADC Optional Sizing Parameters - * @{ - */ - /** - * @brief The maximum simultaneous GADC low speed device conversions - * @details Defaults to 4 - * @note This value must be less than the number of conversions that can occur - * in a single high speed ADC cycle including the high speed ADC conversion. - * For example, if the ADC can run at 132k samples per second and the high speed - * virtual ADC is using 44kHz then GADC_MAX_LOWSPEED_DEVICES should be set to - * 132/44 - 1 = 2 - */ - #ifndef GADC_MAX_LOWSPEED_DEVICES - #define GADC_MAX_LOWSPEED_DEVICES 4 - #endif -/** @} */ - -#endif /* _GADC_OPTIONS_H */ -/** @} */ +/* + ChibiOS/GFX - Copyright (C) 2012, 2013 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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. + + ChibiOS/GFX 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, see . +*/ + +/** + * @file include/gadc/options.h + * @brief GADC - Periodic ADC subsystem options header file. + * + * @addtogroup GADC + * @{ + */ + +#ifndef _GADC_OPTIONS_H +#define _GADC_OPTIONS_H + +/** + * @name GADC Functionality to be included + * @{ + */ +/** + * @} + * + * @name GADC Optional Sizing Parameters + * @{ + */ + /** + * @brief The maximum GADC sample rate + * @details Defaults to 44000 + * @note This value must be less than half the maximum sample rate allowed by the CPU. + * This is to ensure there is time between high speed samples to perform low + * speed device sampling. + */ + #ifndef GADC_MAX_HIGH_SPEED_SAMPLERATE + #define GADC_MAX_HIGH_SPEED_SAMPLERATE 44000 + #endif +/** @} */ + +#endif /* _GADC_OPTIONS_H */ +/** @} */ diff --git a/include/gfx_rules.h b/include/gfx_rules.h index c132de54..ce6bea50 100644 --- a/include/gfx_rules.h +++ b/include/gfx_rules.h @@ -1,128 +1,136 @@ -/* - ChibiOS/GFX - Copyright (C) 2012 - Joel Bodenmann aka Tectu - - This file is part of ChibiOS/GFX. - - ChibiOS/GFX 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. - - ChibiOS/GFX 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, see . -*/ - -/** - * @file include/gfx_rules.h - * @brief GFX system safety rules header file. - * - * @addtogroup GFX - * @{ - */ - -#ifndef _GFX_RULES_H -#define _GFX_RULES_H - -/** - * Safety checks on all the defines. - * - * These are defined in the order of their inter-dependancies. - */ - -#if GFX_USE_GWIN - #if !GFX_USE_GDISP - #error "GWIN: GFX_USE_GDISP must be TRUE when using GWIN" - #endif - #if !GDISP_NEED_CLIP - #warning "GWIN: Drawing can occur outside the defined windows as GDISP_NEED_CLIP is FALSE" - #endif - #if GWIN_NEED_BUTTON - #if !GDISP_NEED_TEXT - #error "GWIN: GDISP_NEED_TEXT is required if GWIN_NEED_BUTTON is TRUE." - #endif - #if !GFX_USE_GEVENT - #warning "GWIN: GFX_USE_GEVENT is required if GWIN_NEED_BUTTON is TRUE. It has been turned on for you." - #undef GFX_USE_GEVENT - #define GFX_USE_GEVENT TRUE - #endif - #if !GFX_USE_GINPUT || !(GINPUT_NEED_MOUSE || GINPUT_NEED_TOGGLE) - #warning "GWIN: You have set GWIN_NEED_BUTTON to TRUE but no supported GINPUT (mouse/toggle) devices have been included" - #endif - #if !GDISP_NEED_MULTITHREAD && !GDISP_NEED_ASYNC - #warning "GWIN: Either GDISP_NEED_MULTITHREAD or GDISP_NEED_ASYNC is required if GWIN_NEED_BUTTON is TRUE." - #warning "GWIN: GDISP_NEED_MULTITHREAD has been turned on for you." - #undef GDISP_NEED_MULTITHREAD - #define GDISP_NEED_MULTITHREAD TRUE - #endif - #endif - #if GWIN_NEED_CONSOLE - #if !GDISP_NEED_TEXT - #error "GWIN: GDISP_NEED_TEXT is required if GWIN_NEED_CONSOLE is TRUE." - #endif - #endif - #if GWIN_NEED_GRAPH - #endif -#endif - -#if GFX_USE_GINPUT - #if !GFX_USE_GEVENT - #warning "GINPUT: GFX_USE_GEVENT is required if GFX_USE_GINPUT is TRUE. It has been turned on for you." - #undef GFX_USE_GEVENT - #define GFX_USE_GEVENT TRUE - #endif - #if !GFX_USE_GTIMER - #warning "GINPUT: GFX_USE_GTIMER is required if GFX_USE_GINPUT is TRUE. It has been turned on for you." - #undef GFX_USE_GTIMER - #define GFX_USE_GTIMER TRUE - #endif -#endif - -#if GFX_USE_GDISP - #if GDISP_NEED_MULTITHREAD && GDISP_NEED_ASYNC - #error "GDISP: Only one of GDISP_NEED_MULTITHREAD and GDISP_NEED_ASYNC should be defined." - #endif - #if GDISP_NEED_ASYNC - #if !GDISP_NEED_MSGAPI - #warning "GDISP: Messaging API is required for Async Multi-Thread. It has been turned on for you." - #undef GDISP_NEED_MSGAPI - #define GDISP_NEED_MSGAPI TRUE - #endif - #endif -#endif - -#if GFX_USE_TDISP -#endif - -#if GFX_USE_GEVENT - #if !CH_USE_MUTEXES || !CH_USE_SEMAPHORES - #error "GEVENT: CH_USE_MUTEXES and CH_USE_SEMAPHORES must be defined in chconf.h" - #endif -#endif - -#if GFX_USE_GTIMER - #if GFX_USE_GDISP && !GDISP_NEED_MULTITHREAD && !GDISP_NEED_ASYNC - #warning "GTIMER: Neither GDISP_NEED_MULTITHREAD nor GDISP_NEED_ASYNC has been specified." - #warning "GTIMER: Make sure you are not performing any GDISP/GWIN drawing operations in the timer callback!" - #endif -#endif - -#if GFX_USE_GAUDIN -#endif - -#if GFX_USE_GAUDOUT -#endif - -#if GFX_USE_GADC -#endif - -#if GFX_USE_GMISC -#endif - -#endif /* _GFX_H */ -/** @} */ +/* + ChibiOS/GFX - Copyright (C) 2012 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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. + + ChibiOS/GFX 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, see . +*/ + +/** + * @file include/gfx_rules.h + * @brief GFX system safety rules header file. + * + * @addtogroup GFX + * @{ + */ + +#ifndef _GFX_RULES_H +#define _GFX_RULES_H + +/** + * Safety checks on all the defines. + * + * These are defined in the order of their inter-dependancies. + */ + +#if GFX_USE_GWIN + #if !GFX_USE_GDISP + #error "GWIN: GFX_USE_GDISP must be TRUE when using GWIN" + #endif + #if !GDISP_NEED_CLIP + #warning "GWIN: Drawing can occur outside the defined windows as GDISP_NEED_CLIP is FALSE" + #endif + #if GWIN_NEED_BUTTON + #if !GDISP_NEED_TEXT + #error "GWIN: GDISP_NEED_TEXT is required if GWIN_NEED_BUTTON is TRUE." + #endif + #if !GFX_USE_GEVENT + #warning "GWIN: GFX_USE_GEVENT is required if GWIN_NEED_BUTTON is TRUE. It has been turned on for you." + #undef GFX_USE_GEVENT + #define GFX_USE_GEVENT TRUE + #endif + #if !GFX_USE_GINPUT || !(GINPUT_NEED_MOUSE || GINPUT_NEED_TOGGLE) + #warning "GWIN: You have set GWIN_NEED_BUTTON to TRUE but no supported GINPUT (mouse/toggle) devices have been included" + #endif + #if !GDISP_NEED_MULTITHREAD && !GDISP_NEED_ASYNC + #warning "GWIN: Either GDISP_NEED_MULTITHREAD or GDISP_NEED_ASYNC is required if GWIN_NEED_BUTTON is TRUE." + #warning "GWIN: GDISP_NEED_MULTITHREAD has been turned on for you." + #undef GDISP_NEED_MULTITHREAD + #define GDISP_NEED_MULTITHREAD TRUE + #endif + #endif + #if GWIN_NEED_CONSOLE + #if !GDISP_NEED_TEXT + #error "GWIN: GDISP_NEED_TEXT is required if GWIN_NEED_CONSOLE is TRUE." + #endif + #endif + #if GWIN_NEED_GRAPH + #endif +#endif + +#if GFX_USE_GINPUT + #if !GFX_USE_GEVENT + #warning "GINPUT: GFX_USE_GEVENT is required if GFX_USE_GINPUT is TRUE. It has been turned on for you." + #undef GFX_USE_GEVENT + #define GFX_USE_GEVENT TRUE + #endif + #if !GFX_USE_GTIMER + #warning "GINPUT: GFX_USE_GTIMER is required if GFX_USE_GINPUT is TRUE. It has been turned on for you." + #undef GFX_USE_GTIMER + #define GFX_USE_GTIMER TRUE + #endif +#endif + +#if GFX_USE_GDISP + #if GDISP_NEED_MULTITHREAD && GDISP_NEED_ASYNC + #error "GDISP: Only one of GDISP_NEED_MULTITHREAD and GDISP_NEED_ASYNC should be defined." + #endif + #if GDISP_NEED_ASYNC + #if !GDISP_NEED_MSGAPI + #warning "GDISP: Messaging API is required for Async Multi-Thread. It has been turned on for you." + #undef GDISP_NEED_MSGAPI + #define GDISP_NEED_MSGAPI TRUE + #endif + #endif +#endif + +#if GFX_USE_TDISP +#endif + +#if GFX_USE_GADC + #if !CH_USE_MUTEXES || !CH_USE_SEMAPHORES + #error "GADC: CH_USE_MUTEXES and CH_USE_SEMAPHORES must be defined in chconf.h" + #endif + #if !GFX_USE_GTIMER + #warning "GADC: GFX_USE_GTIMER is required if GFX_USE_GADC is TRUE. It has been turned on for you." + #undef GFX_USE_GTIMER + #define GFX_USE_GTIMER TRUE + #endif +#endif + +#if GFX_USE_GEVENT + #if !CH_USE_MUTEXES || !CH_USE_SEMAPHORES + #error "GEVENT: CH_USE_MUTEXES and CH_USE_SEMAPHORES must be defined in chconf.h" + #endif +#endif + +#if GFX_USE_GTIMER + #if GFX_USE_GDISP && !GDISP_NEED_MULTITHREAD && !GDISP_NEED_ASYNC + #warning "GTIMER: Neither GDISP_NEED_MULTITHREAD nor GDISP_NEED_ASYNC has been specified." + #warning "GTIMER: Make sure you are not performing any GDISP/GWIN drawing operations in the timer callback!" + #endif +#endif + +#if GFX_USE_GAUDIN +#endif + +#if GFX_USE_GAUDOUT +#endif + +#if GFX_USE_GMISC +#endif + +#endif /* _GFX_H */ +/** @} */ diff --git a/src/gadc/gadc.c b/src/gadc/gadc.c index 5533bb49..509557d3 100644 --- a/src/gadc/gadc.c +++ b/src/gadc/gadc.c @@ -1,38 +1,465 @@ -/* - ChibiOS/GFX - Copyright (C) 2012 - Joel Bodenmann aka Tectu - - This file is part of ChibiOS/GFX. - - ChibiOS/GFX 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. - - ChibiOS/GFX 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, see . -*/ - -/** - * @file src/gadc/gadc.c - * @brief GADC sub-system code. - * - * @addtogroup GADC - * @{ - */ -#include "ch.h" -#include "hal.h" -#include "gfx.h" - -#if GFX_USE_GADC || defined(__DOXYGEN__) - - #error "GADC: Not implemented yet" - -#endif /* GFX_USE_GADC */ -/** @} */ - +/* + ChibiOS/GFX - Copyright (C) 2012, 2013 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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. + + ChibiOS/GFX 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, see . +*/ + +/** + * @file src/gadc/gadc.c + * @brief GADC sub-system code. + * + * @addtogroup GADC + * @{ + */ +#include "ch.h" +#include "hal.h" +#include "gfx.h" + +#if GFX_USE_GADC + +/* Include the driver defines */ +#include "gadc/lld/gadc_lld.h" + +#if GADC_MAX_HIGH_SPEED_SAMPLERATE > GADC_MAX_SAMPLE_FREQUENCY/2 + #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 + +volatile bool_t GADC_Timer_Missed; + +static SEMAPHORE_DECL(gadcsem, GADC_MAX_LOWSPEED_DEVICES); +static MUTEX_DECL(gadcmutex); +static GTIMER_DECL(LowSpeedGTimer); +#if GFX_USE_GEVENT + static GTIMER_DECL(HighSpeedGTimer); +#endif + +static volatile uint16_t gflags = 0; + #define GADC_GFLG_INITDONE 0x0001 + #define GADC_GFLG_ISACTIVE 0x0002 + +#define GADC_FLG_ISACTIVE 0x0001 +#define GADC_FLG_ISDONE 0x0002 +#define GADC_FLG_ERROR 0x0004 +#define GADC_FLG_GTIMER 0x0008 + +static struct hsdev { + // Our status flags + uint16_t flags; + + // What we started with + uint32_t frequency; + adcsample_t *buffer; + size_t bufcount; + size_t samplesPerEvent; + + // The last set of results + size_t lastcount; + adcsample_t *lastbuffer; + uint16_t lastflags; + + // Other stuff we need to track progress and for signalling + GadcLldTimerData lld; + size_t samplesPerConversion; + size_t remaining; + BinarySemaphore *bsem; + GEventADC *pEvent; + } 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. + */ + while(curlsdev < &ls[GADC_MAX_LOWSPEED_DEVICES]) { + if ((curlsdev->flags & (GADC_FLG_ISACTIVE|GADC_FLG_ISDONE)) == GADC_FLG_ISACTIVE) { + gadc_lld_adc_nontimerI(&curlsdev->lld); + return; + } + curlsdev++; + } + curlsdev = 0; + + /* No more low speed devices - do a high speed conversion */ + if (hs.flags & GADC_FLG_ISACTIVE) { + hs.lld.now = GADC_Timer_Missed ? TRUE : FALSE; + GADC_Timer_Missed = 0; + gadc_lld_adc_timerI(&hs.lld); + return; + } + + /* Nothing more to do */ + gflags &= ~GADC_GFLG_ISACTIVE; +} + +void GADC_ISR_CompleteI(ADCDriver *adcp, adcsample_t *buffer, size_t n) { + (void) adcp; + + if (curlsdev) { + /* This interrupt must be in relation to the low speed device */ + + if (curlsdev->flags & GADC_FLG_ISACTIVE) { + /** + * As we only handle a single low speed conversion at a time, we know + * we know we won't get any half completion interrupts. + */ + curlsdev->flags |= GADC_FLG_ISDONE; + gtimerJabI(&LowSpeedGTimer); + } + + #if 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. + */ + gflags &= ~GADC_GFLG_ISACTIVE; + return; + + #endif + + } else { + /* This interrupt must be in relation to the high speed device */ + + if (hs.flags & GADC_FLG_ISACTIVE) { + /* Save the details */ + hs.lastcount = n; + hs.lastbuffer = buffer; + hs.lastflags = GADC_Timer_Missed ? GADC_HSADC_LOSTEVENT : 0; + + /* Signal the user with the data */ + if (hs.pEvent) { + #if GFX_USE_GEVENT + hs.pEvent->type = GEVENT_ADC; + #endif + hs.pEvent->count = hs.lastcount; + hs.pEvent->buffer = hs.lastbuffer; + hs.pEvent->flags = hs.lastflags; + } + if (hs.bsem) + chBSemSignalI(hs.bsem); + + #if GFX_USE_GEVENT + if (hs.flags & GADC_FLG_GTIMER) + gtimerJabI(&HighSpeedGTimer); + #endif + + /* Adjust what we have left to do */ + hs.lld.count -= n; + hs.remaining -= n; + + /* Half completion - We have done all we can for now - wait for the next interrupt */ + if (hs.lld.count) + return; + + /* Our buffer is cyclic - set up the new buffer pointers */ + if (hs.remaining) { + hs.lld.buffer = buffer + (n * hs.samplesPerConversion); + } else { + hs.remaining = hs.bufcount; + hs.lld.buffer = hs.buffer; + } + hs.lld.count = hs.remaining < hs.samplesPerEvent ? hs.remaining : hs.samplesPerEvent; + } + } + + /** + * Look for the next thing to do. + */ + FindNextConversionI(); +} + +void GADC_ISR_ErrorI(ADCDriver *adcp, adcerror_t err) { + (void) adcp; + (void) err; + + 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 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. + */ + gflags &= ~GADC_GFLG_ISACTIVE; + gtimerJabI(&LowSpeedGTimer); + return; + + #endif + + } 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(); +} + +static __inline void DoInit(void) { + if (!(gflags & GADC_GFLG_INITDONE)) { + gflags |= GADC_GFLG_INITDONE; + gadc_lld_init(); + } +} + +static __inline void StartADC(bool_t onNoHS) { + chSysLock(); + if (!(gflags & GADC_GFLG_ISACTIVE) || (onNoHS && !curlsdev)) + FindNextConversionI(); + chSysUnlock(); +} + +static void BSemSignalCallback(adcsample_t *buffer, void *param) { + (void) buffer; + + /* Signal the BinarySemaphore parameter */ + chBSemSignal((BinarySemaphore *)param); +} + +#if GFX_USE_GEVENT + static void HighSpeedGTimerCallback(void *param) { + (void) param; + GSourceListener *psl; + GEventADC *pe; + + psl = 0; + while ((psl = geventGetSourceListener((GSourceHandle)(&HighSpeedGTimer), psl))) { + if (!(pe = (GEventADC *)geventGetEventBuffer(psl))) { + // This listener is missing - save this. + psl->srcflags |= GADC_HSADC_LOSTEVENT; + continue; + } + + pe->type = GEVENT_ADC; + pe->count = hs.lastcount; + pe->buffer = hs.lastbuffer; + pe->flags = hs.lastflags | 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 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) + chSemSignal(&gadcsem); // Tell everyone + fn(buffer, prm); // Perform the callback + } + } + +} + +void gadcHighSpeedInit(uint32_t physdev, uint32_t frequency, adcsample_t *buffer, size_t bufcount, size_t samplesPerEvent) +{ + gadcHighSpeedStop(); /* This does the init for us */ + + /* Just save the details and reset everything for now */ + hs.frequency = frequency; + hs.buffer = buffer; + hs.bufcount = bufcount; + hs.samplesPerEvent = samplesPerEvent; + hs.lastcount = 0; + hs.lastbuffer = 0; + hs.lastflags = 0; + hs.lld.physdev = physdev; + hs.lld.buffer = buffer; + hs.lld.count = samplesPerEvent; + hs.lld.now = FALSE; + hs.samplesPerConversion = gadc_lld_samples_per_conversion(physdev); + hs.remaining = bufcount; + hs.bsem = 0; + hs.pEvent = 0; +} + +#if GFX_USE_GEVENT + GSourceHandle gadcHighSpeedGetSource(void) { + DoInit(); + if (!gtimerIsActive(&HighSpeedGTimer)) + gtimerStart(&HighSpeedGTimer, HighSpeedGTimerCallback, NULL, TRUE, TIME_INFINITE); + hs.flags |= GADC_FLG_GTIMER; + return (GSourceHandle)&HighSpeedGTimer; + } +#endif + +void gadcHighSpeedSetBSem(BinarySemaphore *pbsem, GEventADC *pEvent) { + DoInit(); + + /* Use the system lock to ensure they occur atomically */ + chSysLock(); + hs.pEvent = pEvent; + hs.bsem = pbsem; + chSysUnlock(); +} + +void gadcHighSpeedStart(void) { + DoInit(); + + /* If its already going we don't need to do anything */ + if (hs.flags & GADC_FLG_ISACTIVE) + return; + + gadc_lld_start_timer(hs.lld.physdev, hs.frequency); + hs.flags = GADC_FLG_ISACTIVE; + StartADC(FALSE); +} + +void gadcHighSpeedStop(void) { + DoInit(); + + if (hs.flags & GADC_FLG_ISACTIVE) { + /* No more from us */ + hs.flags = 0; + gadc_lld_stop_timer(hs.lld.physdev); + /* + * 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); + } +} + +void gadcLowSpeedGet(uint32_t physdev, adcsample_t *buffer) { + struct lsdev *p; + BSEMAPHORE_DECL(mysem, TRUE); + + /* Start the Low Speed Timer */ + chMtxLock(&gadcmutex); + if (!gtimerIsActive(&LowSpeedGTimer)) + gtimerStart(&LowSpeedGTimer, LowSpeedGTimerCallback, NULL, TRUE, TIME_INFINITE); + chMtxUnlock(); + + while(1) { + /* Wait for an available slot */ + chSemWait(&gadcsem); + + /* Find a slot */ + chMtxLock(&gadcmutex); + 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; + chMtxUnlock(); + StartADC(FALSE); + chBSemWait(&mysem); + return; + } + } + chMtxUnlock(); + + /** + * We should never get here - the count semaphore must be wrong. + * Decrement it and try again. + */ + } +} + +bool_t gadcLowSpeedStart(uint32_t physdev, adcsample_t *buffer, GADCCallbackFunction fn, void *param) { + struct lsdev *p; + + DoInit(); + + /* Start the Low Speed Timer */ + chMtxLock(&gadcmutex); + if (!gtimerIsActive(&LowSpeedGTimer)) + gtimerStart(&LowSpeedGTimer, LowSpeedGTimerCallback, NULL, 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 */ + chSemWaitTimeout(&gadcsem, TIME_IMMEDIATE); + p->lld.physdev = physdev; + p->lld.buffer = buffer; + p->fn = fn; + p->param = param; + p->flags = GADC_FLG_ISACTIVE; + chMtxUnlock(); + StartADC(FALSE); + return TRUE; + } + } + chMtxUnlock(); + return FALSE; +} + +#endif /* GFX_USE_GADC */ +/** @} */ +