.br
.br
-Returns 0 if OK, otherwise PI_BAD_HANDLE, PI_BAD_SPI_COUNT, or
-PI_SPI_XFER_FAILED.
+Returns the number of bytes transferred if OK, otherwise
+PI_BAD_HANDLE, PI_BAD_SPI_COUNT, or PI_SPI_XFER_FAILED.
.IP "\fBint spiWrite(unsigned handle, char *buf, unsigned count)\fP"
.IP "" 4
.br
.br
-Returns 0 if OK, otherwise PI_BAD_HANDLE, PI_BAD_SPI_COUNT, or
-PI_SPI_XFER_FAILED.
+Returns the number of bytes transferred if OK, otherwise
+PI_BAD_HANDLE, PI_BAD_SPI_COUNT, or PI_SPI_XFER_FAILED.
.IP "\fBint spiXfer(unsigned handle, char *txBuf, char *rxBuf, unsigned count)\fP"
.IP "" 4
.br
.br
-Returns 0 if OK, otherwise PI_BAD_HANDLE, PI_BAD_SPI_COUNT, or
-PI_SPI_XFER_FAILED.
+Returns the number of bytes transferred if OK, otherwise
+PI_BAD_HANDLE, PI_BAD_SPI_COUNT, or PI_SPI_XFER_FAILED.
.IP "\fBint serOpen(char *sertty, unsigned serBaud, unsigned serFlags)\fP"
.IP "" 4
Or in PI_DISABLE_FIFO_IF to disable the pipe interface.
Or in PI_DISABLE_SOCK_IF to disable the socket interface.
+.IP "\fBint gpioCustom1(unsigned arg1, unsigned arg2, char *argx, unsigned count)\fP"
+.IP "" 4
+This function is available for user customisation.
+
+.br
+
+.br
+It returns a single integer value.
+
+.br
+
+.br
+
+.EX
+ arg1: >=0
+.br
+ arg2: >=0
+.br
+ argx: extra (byte) arguments
+.br
+count: number of extra arguments
+.br
+
+.EE
+
+.br
+
+.br
+Returns >= 0 if OK, less than 0 indicates a user defined error.
+
+.IP "\fBint gpioCustom2(unsigned arg1, char *argx, unsigned count, char *retBuf, unsigned retMax)\fP"
+.IP "" 4
+This function is available for user customisation.
+
+.br
+
+.br
+It differs from gpioCustom1 in that it returns an array of bytes
+rather than just an integer.
+
+.br
+
+.br
+The returned value is an integer indicating the number of returned bytes.
+
+.EX
+ arg1: >=0
+.br
+ argx: extra (byte) arguments
+.br
+ count: number of extra arguments
+.br
+retBuf: buffer for returned bytes
+.br
+retMax: maximum number of bytes to return
+.br
+
+.EE
+
+.br
+
+.br
+Returns >= 0 if OK, less than 0 indicates a user defined error.
+
+.br
+
+.br
+The number of returned bytes must be retMax or less.
+
.IP "\fBint gpioCfgInternals(unsigned cfgWhat, int cfgVal)\fP"
.IP "" 4
Used to tune internal settings.
.br
.EX
-#define PI_HW_PWM_RANGE 1000
+#define PI_HW_PWM_RANGE 5000
.br
.EE
.EX
#define PI_HW_PWM_MIN_FREQ 5
.br
-#define PI_HW_PWM_MAX_FREQ 250000
+#define PI_HW_PWM_MAX_FREQ 50000
.br
.EE
#define PI_CMD_HP 86
.br
+.br
+#define PI_CMD_CF1 87
+.br
+#define PI_CMD_CF2 88
+.br
+
.br
#define PI_CMD_NOIB 99
.br
.br
#define PI_NOT_HPWM_GPIO -95 // gpio has no hardware PWM
.br
-#define PI_BAD_HPWM_FREQ -96 // hardware PWM frequency not 5-250K
+#define PI_BAD_HPWM_FREQ -96 // hardware PWM frequency not 5-50K
.br
-#define PI_BAD_HPWM_DUTY -97 // hardware PWM dutycycle not 0-1000
+#define PI_BAD_HPWM_DUTY -97 // hardware PWM dutycycle not 0-5000
.br
#define PI_BAD_HCLK_FREQ -98 // hardware clock frequency not 4689-25M
.br
.br
#define PI_BAD_STOPBITS -102 // serial (half) stop bits not 2-8
.br
+#define PI_MSG_TOOBIG -103 // socket/pipe message too big
+.br
+
+.br
+#define PI_PIGIF_ERR_0 -2000
+.br
+#define PI_PIGIF_ERR_99 -2099
+.br
+
+.br
+#define PI_CUSTOM_ERR_0 -3000
+.br
+#define PI_CUSTOM_ERR_999 -3999
+.br
.br
For more information, please refer to <http://unlicense.org/>
*/
-/* pigpio version 27 */
+/* pigpio version 28 */
#include <stdio.h>
#include <string.h>
} \
while (0)
-static volatile unsigned int piModel = 1;
-static volatile unsigned int PI_PERI_BASE = 0x20000000;
-static volatile unsigned int DMA_BUS_ADR = 0x40000000;
+#define PI_PERI_PHYS 0x7E000000
+
+static void *dummy = MAP_FAILED;
+
+static volatile uint32_t piModel = 1;
+
+static volatile uint32_t PI_PERI_BASE = 0x20000000;
+static volatile uint32_t DMA_BUS_ADR = 0x40000000;
+static volatile uint32_t DMA_BUS_CACHE = 0x00000000;
#define AUX_BASE (PI_PERI_BASE + 0x00215000)
#define CLK_BASE (PI_PERI_BASE + 0x00101000)
uint32_t emitFrags;
uint32_t maxSamples;
uint32_t numSamples;
+ uint32_t DMARestarts;
+ uint32_t DMAInits;
+ uint32_t dmaInitCbsCount;
} gpioStats_t;
typedef struct
static int gpioNotifyOpenInBand(int fd);
static void initHWClk
(int clkCtl, int clkDiv, int clkSrc, int divI, int divF, int MASH);
+static void initDMAgo(volatile uint32_t *dmaAddr, uint32_t cbAddr);
+static void flushDMA(void);
+
/* ======================================================================= */
static void myLvsPageSlot(int pos, int * page, int * slot)
{
+// *page = pos%DMAI_PAGES;
+// *slot = pos/DMAI_PAGES;
*page = pos/LVS_PER_IPAGE;
*slot = pos%LVS_PER_IPAGE;
}
static void myTckPageSlot(int pos, int * page, int * slot)
{
+// *page = pos%DMAI_PAGES;
+// *slot = pos/DMAI_PAGES;
*page = pos/TCK_PER_IPAGE;
*slot = pos%TCK_PER_IPAGE;
}
p->info = NORMAL_DMA |
DMA_DEST_DREQ |
DMA_PERIPHERAL_MAPPING(2);
- p->dst = ((PCM_BASE + PCM_FIFO*4) & 0x00ffffff) | 0x7e000000;
+ p->dst = ((PCM_BASE + PCM_FIFO*4) & 0x00ffffff) | PI_PERI_PHYS;
}
else
{
p->info = NORMAL_DMA |
DMA_DEST_DREQ |
DMA_PERIPHERAL_MAPPING(5);
- p->dst = ((PWM_BASE + PWM_FIFO*4) & 0x00ffffff) | 0x7e000000;
+ p->dst = ((PWM_BASE + PWM_FIFO*4) & 0x00ffffff) | PI_PERI_PHYS;
}
p->src = (uint32_t) (&dmaOPhys[0]->periphData) | DMA_BUS_ADR;
p->info = NORMAL_DMA;
p->src = waveOOLPOadr(botOOL++) | DMA_BUS_ADR;
- p->dst = ((GPIO_BASE + (GPSET0*4)) & 0x00ffffff) | 0x7e000000;
+ p->dst = ((GPIO_BASE + (GPSET0*4)) & 0x00ffffff) | PI_PERI_PHYS;
p->length = 4;
p->next = waveCbPOadr(botCB) | DMA_BUS_ADR;
}
p->info = NORMAL_DMA;
p->src = waveOOLPOadr(botOOL++) | DMA_BUS_ADR;
- p->dst = ((GPIO_BASE + (GPCLR0*4)) & 0x00ffffff) | 0x7e000000;
+ p->dst = ((GPIO_BASE + (GPCLR0*4)) & 0x00ffffff) | PI_PERI_PHYS;
p->length = 4;
p->next = waveCbPOadr(botCB) | DMA_BUS_ADR;
}
p = rawWaveCBAdr(botCB++);
p->info = NORMAL_DMA;
- p->src = ((GPIO_BASE + (GPLEV0*4)) & 0x00ffffff) | 0x7e000000;
+ p->src = ((GPIO_BASE + (GPLEV0*4)) & 0x00ffffff) | PI_PERI_PHYS;
p->dst = waveOOLPOadr(--topOOL) | DMA_BUS_ADR;
p->length = 4;
p->next = waveCbPOadr(botCB) | DMA_BUS_ADR;
p = rawWaveCBAdr(botCB++);
p->info = NORMAL_DMA;
- p->src = ((SYST_BASE + (SYST_CLO*4)) & 0x00ffffff) | 0x7e000000;
+ p->src = ((SYST_BASE + (SYST_CLO*4)) & 0x00ffffff) | PI_PERI_PHYS;
p->dst = waveOOLPOadr(--topOOL) | DMA_BUS_ADR;
p->length = 4;
p->next = waveCbPOadr(botCB) | DMA_BUS_ADR;
DMA_DEST_DREQ |
DMA_PERIPHERAL_MAPPING(2);
- p->dst = ((PCM_BASE + PCM_FIFO*4) & 0x00ffffff) | 0x7e000000;
+ p->dst = ((PCM_BASE + PCM_FIFO*4) & 0x00ffffff) | PI_PERI_PHYS;
}
else
{
DMA_DEST_DREQ |
DMA_PERIPHERAL_MAPPING(5);
- p->dst = ((PWM_BASE + PWM_FIFO*4) & 0x00ffffff) | 0x7e000000;
+ p->dst = ((PWM_BASE + PWM_FIFO*4) & 0x00ffffff) | PI_PERI_PHYS;
}
p->src = (uint32_t) (&dmaOPhys[0]->periphData) | DMA_BUS_ADR;
if (bit > 1)
SOFT_ERROR(PI_BAD_PARAM, "bad bit (%d)", bit);
- err = my_smbus_access(i2cInfo[handle].fd, bit, 0, PI_I2C_SMBUS_QUICK, NULL);
+ err = my_smbus_access(
+ i2cInfo[handle].fd, bit, 0, PI_I2C_SMBUS_QUICK, NULL);
if (err < 0) return PI_I2C_WRITE_FAILED;
system("/sbin/modprobe i2c_dev");
system("/sbin/modprobe i2c_bcm2708");
- usleep(100000);
+ myGpioDelay(100000);
if ((fd = open(dev, O_RDWR)) < 0)
{
auxReg[AUX_SPI0_CNTL0_REG] =
AUXSPI_CNTL0_ENABLE | AUXSPI_CNTL0_CLR_FIFOS;
- usleep(10);
+ myGpioDelay(10);
auxReg[AUX_SPI0_CNTL0_REG] = AUXSPI_CNTL0_ENABLE | spiDefaults;
char *rxBuf,
unsigned count)
{
- DBG(0, "spiGo");
if (PI_SPI_FLAGS_GET_AUX_SPI(flags))
{
- DBG(0, "spiGoA");
spiGoA(speed, flags, txBuf, rxBuf, count);
}
else
{
- DBG(0, "spiGoS");
spiGoS(speed, flags, txBuf, rxBuf, count);
}
}
endTick = systReg[SYST_CLO];
if (endTick != startTick)
- gpioStats.cbTicks += (endTick - startTick);
- else gpioStats.cbTicks ++;
+ gpioStats.cbTicks += (endTick - startTick);
gpioStats.cbCalls++;
p->info = NORMAL_DMA;
p->src = dmaGpioOnAdr(pos) | DMA_BUS_ADR;
- p->dst = ((GPIO_BASE + (GPSET0*4)) & 0x00ffffff) | 0x7e000000;
+ p->dst = ((GPIO_BASE + (GPSET0*4)) & 0x00ffffff) | PI_PERI_PHYS;
p->length = 4;
p->next = dmaCbAdr(b+1) | DMA_BUS_ADR;
}
p = dmaCB2adr(b);
p->info = NORMAL_DMA;
- p->src = ((SYST_BASE + (SYST_CLO*4)) & 0x00ffffff) | 0x7e000000;
+ p->src = ((SYST_BASE + (SYST_CLO*4)) & 0x00ffffff) | PI_PERI_PHYS;
p->dst = dmaTickAdr(pos) | DMA_BUS_ADR;
p->length = 4;
p->next = dmaCbAdr(b+1) | DMA_BUS_ADR;
p->info = NORMAL_DMA;
p->src = dmaGpioOffAdr(pos) | DMA_BUS_ADR;
- p->dst = ((GPIO_BASE + (GPCLR0*4)) & 0x00ffffff) | 0x7e000000;
+ p->dst = ((GPIO_BASE + (GPCLR0*4)) & 0x00ffffff) | PI_PERI_PHYS;
p->length = 4;
p->next = dmaCbAdr(b+1) | DMA_BUS_ADR;
}
p = dmaCB2adr(b);
p->info = NORMAL_DMA;
- p->src = ((GPIO_BASE + (GPLEV0*4)) & 0x00ffffff) | 0x7e000000;
+ p->src = ((GPIO_BASE + (GPLEV0*4)) & 0x00ffffff) | PI_PERI_PHYS;
p->dst = dmaReadLevelsAdr(pos) | DMA_BUS_ADR;
p->length = 4;
p->next = dmaCbAdr(b+1) | DMA_BUS_ADR;
if (gpioCfg.clockPeriph == PI_CLOCK_PCM)
{
p->info = NORMAL_DMA | TIMED_DMA(2);
- p->dst = ((PCM_BASE + PCM_FIFO*4) & 0x00ffffff) | 0x7e000000;
+ p->dst = ((PCM_BASE + PCM_FIFO*4) & 0x00ffffff) | PI_PERI_PHYS;
}
else
{
p->info = NORMAL_DMA | TIMED_DMA(5);
- p->dst = ((PWM_BASE + PWM_FIFO*4) & 0x00ffffff) | 0x7e000000;
+ p->dst = ((PWM_BASE + PWM_FIFO*4) & 0x00ffffff) | PI_PERI_PHYS;
}
p->src = dmaPwmDataAdr(b%DMAI_PAGES) | DMA_BUS_ADR;
DBG(DBG_STARTUP, "");
+ gpioStats.dmaInitCbsCount++;
+
b = -1;
level = 0;
int numSamples, d;
int b, n, v;
int err;
+ int stopped;
char fifo[32];
req.tv_sec = 0;
cycle = (oldSlot/PULSE_PER_CYCLE);
pulse = (oldSlot%PULSE_PER_CYCLE);
+ stopped = 0;
+
while (1)
{
+
+ if (dmaIn[DMA_CONBLK_AD])
+ {
+ if (stopped)
+ {
+ DBG(1, "****** GOING ******");
+ stopped = 0;
+ }
+ }
+ else
+ {
+ if (!stopped)
+ {
+ DBG(1, "****** STOPPED ******");
+ stopped = 1;
+ }
+ dmaInitCbs();
+ flushDMA();
+ myGpioDelay(5000); /* let DMA run for a while */
+ initDMAgo((uint32_t *)dmaIn, (uint32_t)dmaIPhys[0]);
+ myGpioDelay(5000); /* let DMA run for a while */
+ oldSlot = 0;
+ gpioStats.DMARestarts++;
+ }
+
gpioStats.alertTicks++;
req.tv_nsec = 850000;
flags = fcntl(fileno(outFifo), F_GETFL, 0);
fcntl(fileno(outFifo), F_SETFL, flags | O_NONBLOCK);
+ /* don't start until DMA started */
+
+ while (!DMAstarted) myGpioDelay(1000);
+
+ myGpioDelay(20000); /* let DMA run for a while */
+
+
while (1)
{
if (fgets(buf, sizeof(buf), inpFifo) == NULL)
c = sizeof(struct sockaddr_in);
+ /* don't start until DMA started */
+
+ while (!DMAstarted) myGpioDelay(1000);
+
+ myGpioDelay(20000); /* let DMA run for a while */
+
while ((fdC =
accept(fdSock, (struct sockaddr *)&client, (socklen_t*)&c)))
{
PROT_READ|PROT_WRITE,
MAP_SHARED|MAP_FIXED|MAP_LOCKED|MAP_NORESERVE,
fdMem,
- (uint32_t)dmaP[n] | 0x40000000
+ (uint32_t)dmaP[n] | DMA_BUS_CACHE
);
pageAdr2 += PAGE_SIZE;
return 0;
}
+#define FLUSH_PAGES 1000
+
+static void flushDMA(void)
+{
+ static int val = 0;
+
+ if (dummy != MAP_FAILED) munmap(dummy, FLUSH_PAGES*PAGE_SIZE);
+
+ dummy = MAP_FAILED;
+
+ dummy = mmap(
+ 0, (FLUSH_PAGES*PAGE_SIZE),
+ PROT_READ|PROT_WRITE|PROT_EXEC,
+ MAP_SHARED|MAP_ANONYMOUS|MAP_NORESERVE|MAP_LOCKED,
+ -1, 0);
+
+ if (dummy == MAP_FAILED)
+ {
+ DBG(0, "mmap dummy failed (%m)");
+ }
+ else
+ {
+ memset(dummy, val++, (FLUSH_PAGES*PAGE_SIZE));
+ }
+}
/* ----------------------------------------------------------------------- */
for (i=0; i<DMAI_PAGES; i++)
DBG(DBG_STARTUP, "dmaIPhys[%d]=%08X", i, (uint32_t)dmaIPhys[i]);
- dmaInitCbs();
-
if (gpioCfg.dbgLevel >= DBG_DMACBS)
{
fprintf(stderr, "*** INPUT DMA CONTROL BLOCKS ***\n");
DMA_PANIC_PRIORITY(8) |
DMA_PRIORITY(8) |
DMA_ACTIVATE;
-
- DMAstarted = 1;
}
/* ----------------------------------------------------------------------- */
/* ======================================================================= */
+void startPi1DMA(void)
+{
+ dmaInitCbs();
+
+ flushDMA();
+
+ initDMAgo((uint32_t *)dmaIn, (uint32_t)dmaIPhys[0]);
+
+ gpioStats.DMAInits++;
+}
+
+void startPi2DMA(void)
+{
+ int i, running, looped, firstCB, passedFirst;
+
+ for (i=0; i<150; i++)
+ {
+ dmaInitCbs();
+
+ flushDMA();
+ }
+
+ running = 0;
+
+ while (!running)
+ {
+ dmaInitCbs();
+ flushDMA();
+
+ initDMAgo((uint32_t *)dmaIn, (uint32_t)dmaIPhys[0]);
+
+ gpioStats.DMAInits++;
+
+ myGpioDelay(20000);
+ i = dmaNowAtICB();
+
+ if (i)
+ {
+ firstCB = i;
+
+ passedFirst = 0;
+
+ looped = 0;
+
+ while (looped < 10)
+ {
+ dmaInitCbs();
+ flushDMA();
+
+ myGpioDelay(1000);
+ i = dmaNowAtICB();
+
+ if (i < firstCB)
+ {
+ if (i)
+ {
+ if (passedFirst)
+ {
+ looped++;
+ running = 1;
+ passedFirst = 0;
+ }
+ }
+ else
+ {
+ running = 0;
+ looped = 1000;
+ }
+ }
+ else passedFirst = 1;
+ }
+ }
+ }
+}
+
int gpioInitialise(void)
{
int i;
pthSocketRunning = 1;
}
- initDMAgo((uint32_t *)dmaIn, (uint32_t)dmaIPhys[0]);
+ if (piModel == 1)
+ startPi1DMA();
+ else
+ startPi2DMA();
+
+ DMAstarted = 1;
return PIGPIO_VERSION;
}
+
/* ----------------------------------------------------------------------- */
void gpioTerminate(void)
if (gpioCfg.showStats)
{
- fprintf(stderr, "micros=%d\n", gpioCfg.clockMicros);
+ fprintf(stderr,
+ "micros=%d dmaInitCbs=%d DMA inits=%d DMA restarts=%d\n",
+ gpioCfg.clockMicros, gpioStats.dmaInitCbsCount,
+ gpioStats.DMAInits, gpioStats.DMARestarts);
fprintf(stderr, "samples %u maxSamples %u maxEmit %u emitFrags %u\n",
gpioStats.numSamples, gpioStats.maxSamples,
FILE * filp;
char buf[512];
char term;
+ int chars=4; /* number of chars in revision string */
DBG(DBG_USER, "");
if (rev) return rev;
+ piModel = 0;
+
filp = fopen ("/proc/cpuinfo", "r");
if (filp != NULL)
{
while (fgets(buf, sizeof(buf), filp) != NULL)
{
- if (!strncmp("model name", buf, 10))
+ if (piModel == 0)
{
- if (strstr (buf, "ARMv6") != NULL)
+ if (!strncasecmp("model name", buf, 10))
{
- piModel = 1;
- PI_PERI_BASE = 0x20000000;
- DMA_BUS_ADR = 0x40000000;
- }
- else if (strstr (buf, "ARMv7") != NULL)
- {
- piModel = 2;
- PI_PERI_BASE = 0x3F000000;
- DMA_BUS_ADR = 0xC0000000;
+ if (strstr (buf, "ARMv6") != NULL)
+ {
+ piModel = 1;
+ chars = 4;
+ PI_PERI_BASE = 0x20000000;
+ DMA_BUS_ADR = 0x40000000;
+ DMA_BUS_CACHE = 0x40000000;
+ }
+ else if (strstr (buf, "ARMv7") != NULL)
+ {
+ piModel = 2;
+ chars = 6;
+ PI_PERI_BASE = 0x3F000000;
+ DMA_BUS_ADR = 0xC0000000;
+ DMA_BUS_CACHE = 0x00000000;
+ }
}
}
- if (!strncmp("Revision", buf, 8))
+ if (!strncasecmp("revision", buf, 8))
{
- if (sscanf(buf+strlen(buf)-5, "%x%c", &rev, &term) == 2)
+ if (sscanf(buf+strlen(buf)-(chars+1),
+ "%x%c", &rev, &term) == 2)
{
if (term != '\n') rev = 0;
}
}
}
+
fclose(filp);
}
return rev;
.EX
gpio: see descripton
.br
-PWMfreq: 0 (off) or 5-250K
+PWMfreq: 0 (off) or 5-50K
.br
-PWMduty: 0 (off) to 1000 (fully on).
+PWMduty: 0 (off) to 5000 (fully on).
.br
.EE
.br
.br
-Returns 0 if OK, otherwise PI_BAD_HANDLE, PI_BAD_SPI_COUNT, or
-PI_SPI_XFER_FAILED.
+Returns the number of bytes transferred if OK, otherwise
+PI_BAD_HANDLE, PI_BAD_SPI_COUNT, or PI_SPI_XFER_FAILED.
.IP "\fBint spi_write(unsigned handle, char *buf, unsigned count)\fP"
.IP "" 4
.br
.br
-Returns 0 if OK, otherwise PI_BAD_HANDLE, PI_BAD_SPI_COUNT, or
-PI_SPI_XFER_FAILED.
+Returns the number of bytes transferred if OK, otherwise
+PI_BAD_HANDLE, PI_BAD_SPI_COUNT, or PI_SPI_XFER_FAILED.
.IP "\fBint spi_xfer(unsigned handle, char *txBuf, char *rxBuf, unsigned count)\fP"
.IP "" 4
.br
.br
-Returns 0 if OK, otherwise PI_BAD_HANDLE, PI_BAD_SPI_COUNT, or
-PI_SPI_XFER_FAILED.
+Returns the number of bytes transferred if OK, otherwise
+PI_BAD_HANDLE, PI_BAD_SPI_COUNT, or PI_SPI_XFER_FAILED.
.IP "\fBint serial_open(char *ser_tty, unsigned ser_baud, unsigned ser_flags)\fP"
.IP "" 4
Returns the number of bytes of data available (>=0) if OK,
otherwise PI_BAD_HANDLE.
+.IP "\fBint custom_1(unsigned arg1, unsigned arg2, char *argx, unsigned count)\fP"
+.IP "" 4
+This function is available for user customisation.
+
+.br
+
+.br
+It returns a single integer value.
+
+.br
+
+.br
+
+.EX
+ arg1: >=0
+.br
+ arg2: >=0
+.br
+ argx: extra (byte) arguments
+.br
+count: number of extra arguments
+.br
+
+.EE
+
+.br
+
+.br
+Returns >= 0 if OK, less than 0 indicates a user defined error.
+
+.IP "\fBint custom_2(unsigned arg1, char *argx, unsigned count, char *retBuf, unsigned retMax)\fP"
+.IP "" 4
+This function is available for user customisation.
+
+.br
+
+.br
+It differs from custom_1 in that it returns an array of bytes
+rather than just an integer.
+
+.br
+
+.br
+The return value is an integer indicating the number of returned bytes.
+
+.EX
+ arg1: >=0
+.br
+ argx: extra (byte) arguments
+.br
+ count: number of extra arguments
+.br
+retBuf: buffer for returned data
+.br
+retMax: maximum number of bytes to return
+.br
+
+.EE
+
+.br
+
+.br
+Returns >= 0 if OK, less than 0 indicates a user defined error.
+
+.br
+
+.br
+Note, the number of returned bytes will be retMax or less.
+
.IP "\fBint callback(unsigned user_gpio, unsigned edge, CBFunc_t f)\fP"
.IP "" 4
This function initialises a new callback.