void record_manager::start() { // Critical Section /////////////////////////////////////////////////////////////////////////// ALLOCATE_WRITE(mutex_); read_count(); const auto minimum = header_size_ + record_to_position(record_count_); if (minimum > file_.size()) throw std::runtime_error("Records size exceeds file size."); /////////////////////////////////////////////////////////////////////////// }
/*** void forceIdleState(void) * * Parameters: * None * * Return Values: * None * * Description: * This put the controller and state machine back to the Idle state * to either the Master or Slave state machines * * ------------------------------------------------------------ */ void DTWI::forceIdleState(void) { uint32_t tStart = 0; uint32_t tCur = 0; uint32_t IEFlags = pregIec->reg & bitMASK; // disable interrupts pregIec->clr = bitMASK; // if we are going to turn off the controller // nothing in the interrupt flags is going to make sense // just clear them pregIfs->clr = bitMASK; // go back to the idle state curState = I2C_IDLE; // toggle the controller.... very ugly! ptwi->i2cCon.ON = 0; // let the controller settle read_count(tStart); do { read_count(tCur); } while(tCur - tStart < CORETIMER_TICKS_PER_MICROSECOND); // turn the controller back on ptwi->i2cCon.ON = 1; // let the controller settle read_count(tStart); do { read_count(tCur); } while(tCur - tStart < CORETIMER_TICKS_PER_MICROSECOND); // go back to where we were with interrupts pregIec->set = IEFlags; }
int pcf_getcount(const char *filename, unsigned long *count) { FILE *f; /* Should we use a page count file? */ if (filename == NULL || *filename == '\0') return 0; /* If the file does not exist, the page count is taken to be zero. */ if (access(filename, F_OK) != 0) { *count = 0; return 0; } /* Open the file */ if ((f = fopen(filename, "r")) == NULL) { fprintf(stderr, ERRPREFIX "Cannot open page count file `%s': %s.\n", filename, strerror(errno)); return -1; } /* Lock the file for reading (shared lock) */ if (lock_file(filename, f, F_RDLCK) != 0) { fclose(f); return 1; } /* Read the contents */ if (read_count(filename, f, count) != 0) { fclose(f); return -1; } /* Close the file, releasing the lock */ fclose(f); return 0; }
/*** bool stopMaster(void) * * Parameters: * None * * Return Values: * True if the a stop condition was asserted on the bus, false if still writing data or * reading data. If true, the I2C interrupts are disabled. If false, continue calling stopMaster() until it succeeds. * * Description: * Assert a stop condition on the I2C bus * You may not be done transimitting data, so call this until it succeeds and you know your data went out. * ------------------------------------------------------------ */ bool DTWI::stopMaster(void) { // if not in master mode or not enabled, Master is stopped if(!fMasterMode || !ptwi->i2cCon.ON) { return(true); } fStop = true; // kick off the state machine and get things writing primeMasterSlave(); // assert the stop condition if(curState == I2C_WAIT) { // put the state machine in the idle state curState = I2C_IDLE; // the lower 5 bits must be set to zero while(ptwi->i2cStat.TRSTAT || (ptwi->ixCon.reg & 0b11111)); // assert the stop condition #if defined(__PIC32MZXX__) if(ptwi->i2cStat.ACKSTAT) { // assert the stop condition ptwi->i2cCon.PEN = 1; } else { uint32_t tStart = 0; uint32_t tCur = 0; uint8_t port = digitalPinToPort(pinSDA); p32_ioport * iop = (p32_ioport *)portRegisters(port); uint32_t bit = digitalPinToBitMask(pinSDA); iop->lat.clr = bit; // put a zero in the latch iop->tris.clr = bit; // make it an output // turn off the I2C controller // the low value of the latch will pull // the SDA low (hold it low). ptwi->i2cCon.ON = 0; // wait some time read_count(tStart); do { read_count(tCur); } while(tCur - tStart < 5 * CORETIMER_TICKS_PER_MICROSECOND); // tri-state the SDA, let the pull up // resistor pull it up iop->tris.set = bit; // we have to put some time on the stop condtion read_count(tStart); do { read_count(tCur); } while(tCur - tStart < 10 * CORETIMER_TICKS_PER_MICROSECOND); // this will clear the controller forceIdleState(); } #else // assert the stop condition ptwi->i2cCon.PEN = 1; #endif } // if the stop condition has completed or bus error; disable interrupts else if(!(ptwi->ixCon.reg & 0b11111) && (curState == I2C_IDLE || curState == I2C_BUS_ERROR)) { // turn off interrupts pregIec->clr = bitMASK; pregIfs->clr = bitMASK; addr = addrGenCall; forceIdleState(); return(true); } return(false); }
/*** bool startMaster(uint8_t addrSlave, uint32_t cbRead, bool fRead) * * Parameters: * addrSlave: The Slave address the Master wishes to talk too. * cbRead: How many bytes to read before sending a Nak, this can be changed by setNAK * fRead: True if we want to start a read from the slave, false if we want to write to the slave. * * Return Values: * True if we got the bus and the slave responded, False if we did not get the connection with the slave * * Description: * Asserts a START condition on the I2C bus, puts the address out AND sets the R /W bit and enables the I2C interrupts * startMaster() can be called before stopMaster(); and if done this way while the application own the bus, it will cause a repeated start condition to be asserted on the bus * If this call fails, someone else may have the bus, or a previous startMaster() may be finishing up writing out the tranmit buffer, continue to call startMaster() until it passes * however, be careful as if someone else has the bus, this may never return true as the bus never becomes available. * If the applicaiton currently does not own the bus, and this call fails, the I2C interrupts will be disabled. * This call is used to implement both * startMasterRead * startMasterWrite * ------------------------------------------------------------ */ bool DTWI::startMaster(uint8_t addrSlave, uint32_t cbRead, bool fRead) { uint8_t volatile rcvB; // make volatile so the compiler will actually read the recv buffer and not opt out uint32_t tCur = 0; uint32_t tStart = 0; // Not in Master mode, or we still have stuff to transmit, or we initiated a stop condition if(!fMasterMode || ptwi->i2cStat.TRSTAT || (ptwi->ixCon.reg & 0b11111)) { return(false); } // we don't want to take an interrupt right now // we want to manually poll that the addr was accepted pregIec->clr = bitMASK; pregIfs->clr = bitMASK; // if we have something in the read buffer, just get it out rcvB = ptwi->ixRcv.reg; // clear error flags ptwi->ixStat.reg = 0; // If there is a start condition on the bus and we own the bus // we need to do a repeat start if(ptwi->i2cStat.S && curState != I2C_IDLE) { // go back to the idle state curState = I2C_IDLE; // the lower 5 bits must be set to zero while(ptwi->ixCon.reg & 0b11111); ptwi->i2cCon.RSEN = 1; // wait repeat start to complete while(ptwi->i2cCon.RSEN); } // if there is no start condition on the bus else if(!ptwi->i2cStat.S) { // force an idle state curState = I2C_IDLE; // the lower 5 bits must be set to zero while(ptwi->ixCon.reg & 0b11111); // set the start event ptwi->i2cCon.SEN = 1; // wait for the start condition to complete while(ptwi->i2cCon.SEN); } // someone else has the bus else { return(false); } // someone else has the bus, it could be a client had SDA held low if(ptwi->i2cStat.BCL) { return(false); } fStop = false; // Write out the address and R/W bit ptwi->ixTrn.reg = (addrSlave << 1) | fRead; // required settling time on the transmit buffer read_count(tStart); do { read_count(tCur); } while(tCur - tStart < (CORETIMER_TICKS_PER_MICROSECOND / 4)); // release the stretch so we will transmit the byte // ptwi->i2cCon.SCLREL = 1; // quit if we get a bus conflict OR the transmit compiletes // if the TRSTAT get set, we know we got the ACK/NAK while(ptwi->i2cStat.BCL == 0 && ptwi->i2cStat.TRSTAT); // got a bus collision; we are done and don't have the bus if(ptwi->i2cStat.BCL) { return(false); } // we got the bus, but the address failed to NAK // there is no device so we want to release the bus and get out else if(ptwi->i2cStat.ACKSTAT) { // the lower 5 bits must be set to zero while(ptwi->ixCon.reg & 0b11111); // assert the stop condition ptwi->i2cCon.PEN = 1; return(false); } // all is good! addr = addrSlave; iSession++; // if this is a MASTER READ if(fRead) { // how many bytes to read before the NAK cbToNAK = cbRead; // say we are recieving data curState = I2C_DATA_READ; // enable recieving data from the slave ptwi->i2cCon.RCEN = 1; } // if this is a MASTER WRITE else { // say we are recieving data curState = I2C_DATA_WRITE; } // re-enable the interrupts pregIfs->clr = bitMASK; pregIec->set = bitMASK; // see if we got the ACK, or NAK or bus collision return(true); }
/*** slaveMachine * * Parameters: * None * * Return Values: * None * * Description: * This is the slave state machine called by the interrupt routine. * * ------------------------------------------------------------ */ void __attribute__((nomips16)) DTWI::slaveMachine(void) { I2C_STATE passState; // we must loop because we my need to transmit after the address // so keep looping until the state is repeated do { passState = curState; switch(curState) { case I2C_IDLE: if(ptwi->i2cStat.RBF) { // what we are expecting is and address being sent if(!ptwi->i2cStat.D_A) { uint8_t rcvB = ptwi->ixRcv.reg; // save the address away, this might be different than the init addr // if we enabled the fGenCall, it might be the general call address // remember to shift off the R/W bit addr = rcvB >> 1; iSession++; // say we saw a start condition // see if this is a Read or Write from the Master perspective // or Write or Read from the slave perspective if(ptwi->i2cStat.R_W) { curState = I2C_DATA_WRITE; // Master R, slave W ptwi->i2cStat.IWCOL = 0; // clear write collisions } // we are not excepting data else if(cbToNAK == 0) { curState = I2C_IDLE; // we are not doing reads ptwi->i2cStat.I2COV = 1; // make it so we NAK } else { curState = I2C_DATA_READ; // Master W, slave R ptwi->i2cStat.I2COV = 0; // clear overflow // not needed on an MX as this is auto released on an address // read, not so on an MZ. we need to release it // buffer because ptwi->i2cCon.SCLREL = 1; // release the clk stretching } } } // I just don't know what this is // in the idle state I need to get an address... or I just don't know what to do with it // Just get rid of it... else { uint8_t rcvB = ptwi->ixRcv.reg; ptwi->i2cStat.I2COV = 0; // clear overflow ptwi->i2cCon.SCLREL = 1; // release the clk stretching } break; case I2C_DATA_READ: if(ptwi->i2cStat.RBF && iReadLast - iReadNext < sizeof(rgbIn)) { rgbIn[iReadLast % sizeof(rgbIn)] = ptwi->ixRcv.reg; iReadLast++; if(cbToNAK != recvInfinite && cbToNAK != 0) { cbToNAK--; } // if we are done reading data, release the clk and go // to an I2C_WAIT state if(cbToNAK == 0) { curState = I2C_WAIT; // we are done with the reads } ptwi->i2cCon.SCLREL = 1; // release the clk stretching } break; case I2C_DATA_WRITE: // get a NAK from the other side // I really don't know what to do because // the master is clocking me, but he asked me to stop // transmitting. So just release the clock and quit if(ptwi->i2cStat.ACKSTAT) { curState = I2C_IDLE; } // normal case, the write register is open else if(!ptwi->i2cStat.TBF) { // we have something to write if(iWriteNext < iWriteLast) { uint32_t tStart; uint32_t tCur; // load the buffer ptwi->ixTrn.reg = rgbOut[iWriteNext % sizeof(rgbOut)]; // we need 250ns (1/4 usec) for setup time read_count(tStart); // update the next pointer // it is okay to go beyond the buffer // because we use the modulo, and the write // funciton will adjust the index down iWriteNext++; // required settling time on the transmit buffer do { read_count(tCur); } while(tCur - tStart < (CORETIMER_TICKS_PER_MICROSECOND / 4)); // release the stretch so we will transmit the byte ptwi->i2cCon.SCLREL = 1; } // if we are to stop else if(fStop) { curState = I2C_WAIT; } } break; case I2C_WAIT: ptwi->i2cStat.I2COV = 1; // Cause a NAK ptwi->i2cCon.SCLREL = 1; // release the clk curState = I2C_IDLE; break; case I2C_BUS_ERROR: default: break; }
/*** masterMachine * * Parameters: * None * * Return Values: * None * * Description: * This is the master state machine called by the interrupt service routine * * ------------------------------------------------------------ */ void __attribute__((nomips16)) DTWI::masterMachine(void) { switch(curState) { case I2C_ACK: // if it was a NAK we are done, just wait if(ptwi->i2cStat.ACKSTAT || fStop) { curState = I2C_WAIT; } // otherwise initiate for another read // if we have room for another byte, lets recieve it // otherwise we will just halt the clock until we can read // some more data. else if((iReadLast - iReadNext) < sizeof(rgbIn)) { // the lower 5 bits must be set to zero while(ptwi->ixCon.reg & 0b11111); curState = I2C_DATA_READ; ptwi->i2cCon.RCEN = 1; } break; // got a read case I2C_DATA_READ: if(ptwi->i2cStat.RBF) { rgbIn[iReadLast % sizeof(rgbIn)] = ptwi->ixRcv.reg; iReadLast++; if(cbToNAK != recvInfinite) { cbToNAK--; } curState = I2C_ACK; ptwi->i2cCon.ACKDT = (cbToNAK == 0 || fStop); // /ACK NAK value ptwi->i2cCon.ACKEN = 1; //send the ACK } // we are told to finish else if(fStop) { curState = I2C_WAIT; } break; // if we have a write case I2C_DATA_WRITE: // only if the transmit engine is idle if(!ptwi->i2cStat.TRSTAT && !ptwi->i2cStat.TBF) { // get a NAK from the other side, or asked to stop by user if(ptwi->i2cStat.ACKSTAT || (fStop && iWriteNext == iWriteLast)) { curState = I2C_WAIT; } // otherwise keep writing data out else if(iWriteNext < iWriteLast) { uint32_t tStart; uint32_t tCur; ptwi->ixTrn.reg = rgbOut[iWriteNext % sizeof(rgbOut)]; // we need 250ns (1/4 usec) for setup time read_count(tStart); // update the next pointer // it is okay to go beyond the buffer // because we use the modulo, and the write // funciton will adjust the index down iWriteNext++; // required settling time on the transmit buffer do { read_count(tCur); } while(tCur - tStart < (CORETIMER_TICKS_PER_MICROSECOND / 4)); // release the stretch so we will transmit the byte // ptwi->i2cCon.SCLREL = 1; } } break; case I2C_WAIT: case I2C_IDLE: case I2C_BUS_ERROR: default: break; } }
int pcf_inccount(const char *filename, unsigned long by) { FILE *f; int rc; unsigned long count; /* Should we use a page count file? */ if (filename == NULL || *filename == '\0') return 0; /* Open the file. We need to access the old contents: this excludes the "w", "a" and "w+" modes. The operation should create the file if it doesn't exist: this excludes the "r" and "r+" modes. Hence the only choice is "a+". Note that this procedure makes it unavoidable to accept an empty file as being valid. This is, however, anyway necessary because of the fopen() and fcntl() calls being not in one transaction. */ if ((f = fopen(filename, "a+")) == NULL) { fprintf(stderr, ERRPREFIX "Cannot open page count file `%s': %s.\n", filename, strerror(errno)); return 1; } /* Lock the file for writing (exclusively) */ if (lock_file(filename, f, F_WRLCK) != 0) { fclose(f); return 1; } /* Reposition on the beginning. fopen() with "a" as above opens the file at EOF. */ if (fseek(f, 0L, SEEK_SET) != 0) { fprintf(stderr, ERRPREFIX "fseek() failed on `%s': %s.\n", filename, strerror(errno)); fclose(f); return 1; } /* Read the contents */ if (read_count(filename, f, &count) != 0) { fclose(f); return -1; } /* Rewrite the file */ rc = 0; { FILE *f1 = fopen(filename, "w"); if (f1 == NULL) { fprintf(stderr, ERRPREFIX "Error opening page count file `%s' a second time: %s.\n", filename, strerror(errno)); rc = 1; } else { if (fprintf(f1, "%lu\n", count + by) < 0) { fprintf(stderr, ERRPREFIX "Error writing to `%s': %s.\n", filename, strerror(errno)); rc = -1; } if (fclose(f1) != 0) { fprintf(stderr, ERRPREFIX "Error closing `%s' after writing: %s.\n", filename, strerror(errno)); rc = -1; } } } /* Close the file (this releases the lock) */ if (fclose(f) != 0) fprintf(stderr, WARNPREFIX "Error closing `%s': %s.\n", filename, strerror(errno)); return rc; }
/*** InitWS2812(uint8_t * pPatternBuffer, uint32_t cbPatternBuffer, uint32_t fInvert) * * Parameters: * pPatternBuffer: A pointer to the pattern buffer for the DMA to use * The application allocates this and should be * CBWS2812PATBUF(__cDevices) bytes long. * * cbPatternBuffer: This should be CBWS2812PATBUF(__cDevices) or larger * * fInvert: Because the WS2812 does not have a VinH <= 3.3 when Vcc >= 4.7v * External hardware may be needed to level shift the 3.3v SDO output * to a higher Data in signal to the WS2812. This level shifter may * be a simple transistor tied to 5v - 7v. The transistor will invert * the SDO output signal and to maintain the correct signal polarity * to the WS2812 you would need to invert the signal coming out of SDO. * If fInvert is true, the SDO output signal will be inverted from what * the WS2812 would normally take. By default, the is "false". * * Return Values: * True if the core timer can be aquired and initialization succeeded. * * Description: * * Initialize the SPI to 20MHz which is a 50ns clock * This will enable .35us pattern resolution in the SPI * shift register. * * Also, initialize 2 DMA channels, one to shift out * the pattern buffer, the other to maintain zeros * for the restart / reset patteren (RES). * * ------------------------------------------------------------ */ uint32_t InitWS2812(uint8_t * pPatternBuffer, uint32_t cbPatternBuffer, uint32_t fInvert) { // Disable SPI and DMA SPI2CON = 0; DMACON = 0; // set up SPI2 SPI2CONbits.MSTEN = 1; // SPI in master mode SPI2CONbits.ENHBUF = 1; // enable 16 byte transfer buffer SPI2CONbits.STXISEL = 0b10; // trigger DMA event when the ENBUF is half empty // SPI2CONbits.DISSDI = 1; // Disable the SDI pin, allow it to be used for GPIO (not implemented on an MX6) SPI2STAT = 0; // clear status register SPI2BRG = (__PIC32_pbClk / (2 * 20000000)) - 1; // 20MHz, 50ns IEC1bits.SPI2RXIE = 0; // disable SPI interrupts IEC1bits.SPI2TXIE = 0; // disable SPI interrupts IEC1bits.SPI2EIE = 0; // disable SPI interrupts IFS1bits.SPI2RXIF = 0; // disable SPI interrupts IFS1bits.SPI2TXIF = 0; // disable SPI interrupts IFS1bits.SPI2EIF = 0; // disable SPI interrupts IEC1bits.DMA0IE = 0; IFS1bits.DMA0IF = 0; IEC1bits.DMA1IE = 0; IFS1bits.DMA1IF = 0; DMACONbits.ON = 1; // turn on the DMA controller IEC1CLR=0x00010000; // disable DMA channel 0 interrupts IFS1CLR=0x00010000; // clear existing DMA channel 0 interrupt flag // Set up DMA channel 0 DCH0CON = 0; // clear it DCH0CONbits.CHAED = 0; // do not allow events to be remembered when disabled DCH0CONbits.CHAEN = 0; // do not Allow continuous operation DCH0CONbits.CHPRI = 0b11; // highest priority DCH0ECON = 0; // clear it DCH0ECONbits.CHSIRQ = _SPI2_TX_IRQ; // SPI2TX 1/2 empty notification DCH0ECONbits.SIRQEN = 1; // enable IRQ transfer enables DCH0INT = 0; // do not trigger any events DCH0INTbits.CHBCIF = 0; // clear IF bit IPC9bits.DMA0IP = 7; // priority 7 IPC9bits.DMA1IS = 0; // sub priority 0 DCH0SSA = KVA_2_PA(pPatternBuffer); // source address of transfer DCH0SSIZ = cbPatternBuffer; // number of bytes in source DCH0DSA = KVA_2_PA(&SPI2BUF); // destination address is the SPI2 buffer DCH0DSIZ = 1; // 1 byte at the destination DCH0CSIZ = 1; // only transfer 1 byte per event // Set up DMA channel 1 DCH1CON = 0; // clear it DCH1CONbits.CHCHNS = 0; // allow chaining to the next higher priority DMA channel 0 DCH1CONbits.CHCHN = 1; // Turn on chaining DCH1CONbits.CHAED = 0; // do not allow events to be remembered when disabled DCH1CONbits.CHAEN = 1; // turn on continuous operation DCH1CONbits.CHPRI = 0b11; // highest priority DCH1ECON = 0; // clear it DCH1ECONbits.CHSIRQ = _SPI2_TX_IRQ; // SPI2TX 1/2 empty notification DCH1ECONbits.SIRQEN = 1; // enable IRQ transfer enables DCH1INT = 0; // do not trigger any events if(fInvert) { DCH1SSA = KVA_2_PA(&ones); // refresh cycle is inverted streaming 1s } else { DCH1SSA = KVA_2_PA(&zeros); // normal refresh cycle streaming 0s } DCH1SSIZ = 1; // number of bytes in source DCH1DSA = KVA_2_PA(&SPI2BUF); // destination address is the SPI2 buffer DCH1DSIZ = 1; // 1 byte at the destination DCH1CSIZ = 1; // only transfer 1 byte per event // initial time for the core serivce routine read_count(tWS2812LastRun); // attach the core service routine if(attachCoreTimerService(WS2812TimerService)) { // Enable DMA channel 1 and SPI 2 // we enable in the refresh cycle until a pattern // is loaded in the main sketch. fWS2812Updating = true; IFS1bits.DMA0IF = 0; IEC1bits.DMA0IE = 1; DCH1CONbits.CHEN = 1; // turn DMA channel 1 ON; just zero output SPI2CONbits.ON = 1; return(1); // success } // Things are not good, disable SPI and DMA SPI2CON = 0; DMACON = 0; // error out return(0); }
// execution begins here int UltraMain(void) { struct display_context * dc; struct controller_data cd; int lasty=0,lastx=0; int d=0; // buffer for rendering text strings (only sprintf works at the moment) char buf[1024]; // offset for the scrolling waveform float offs=0; // used to keep track of how many cycles various processes take unsigned long time,rendertime,lastrendertime,waittime,lastwaittime; // enable MI interrupts (on the CPU) set_MI_interrupt(1); initDisplay(); initAudio(); registerAIhandler(AIcallback); registerVIhandler(VIcallback); while (1) { short * sb; int sbsize; // Mark the value of the COUNT register (increments at half the CPU // rate) at the start of the main loop. time=read_count(); while (! (dc=lockDisplay())) ; // Mark the time after waiting. waittime=time; waittime=((time=read_count())-waittime)*2; sb=getbuf(); sbsize=getbufsize(); for (int c=0;c<sbsize;c++,d++) { sb[c*2]=sin((d%22050)/22050.0*M_TWOPI*1000.0)*100*((cd.c[0].y-lasty)*c/sbsize+lasty)*(((cd.c[0].x-lastx)*c/sbsize+lastx)/128.0-1.0); sb[c*2+1]=sin((d%22050)/22050.0*M_TWOPI*1000.0)*100*((cd.c[0].y-lasty)*c/sbsize+lasty)*(((cd.c[0].x-lastx)*c/sbsize+lastx)/128.0+1.0); } writebuffer(sb); lastx=cd.c[0].x; lasty=cd.c[0].y; // this is what really marks the beginning of the current frame // read controller data controller_Read(&cd); // start drawing on the other frame buffer, clear the screen (uses red // when the start button on controller 1 is pressed, black otherwise) VI_FillScreen(&dc->conf,cd.c[0].err?0:(cd.c[0].start?0xf800:0)); // print a little info for each controller for (int c=0; c < 4; c++) { if (cd.c[c].err) // if controller not present sprintf(buf,"controller %d not present",c+1); else { sprintf(buf,"controller %d present\nbuttons=0x%04X x=%4d y=%4d",c+1, cd.c[c].buttons, cd.c[c].x, cd.c[c].y); } VI_DrawText(&dc->conf,50,58+2*8*c,buf); } // draw a waveform for (float x=50; x < 250; x++) { VI_DrawPixel(&dc->conf,x,cbrt(sin((x-50)/200*M_TWOPI*4+offs))*250.0/8+175,0x7c0); } // the waveform can be scrolled by the x axis of the first controller if (!cd.c[0].err) offs-=(cd.c[0].x/2*2)/32.0; // print up some informative text, and report how long we waited for // this frame, and how long the last frame took to render sprintf(buf,"buffers: %5d frames: %5d\n%8lu cycles last frame\n%8lu cycles waiting\n%4.2f FPS",buffercount,framecount,lastrendertime,lastwaittime,100000000.0/(lastwaittime+lastrendertime)); VI_DrawText(&dc->conf,50,26,buf); // calculate how many cycles this frame took to render rendertime=(read_count()-time)*2; lastwaittime=waittime; lastrendertime=rendertime; showDisplay(dc); dc=0; } return 0; }