sl@8: // sl@8: // sl@8: // sl@8: sl@8: #include "FutabaGP1212A01.h" sl@8: sl@8: const int KNumberOfFrameBeforeDiffAlgo = 3; sl@8: sl@8: // sl@8: // class GP1212A01A sl@8: // sl@8: sl@8: GP1212A01A::GP1212A01A(): sl@8: iDisplayPositionX(0),iDisplayPositionY(0), sl@8: iOffScreenMode(true), sl@8: iUseFrameDifferencing(true), sl@8: iFrameNext(NULL), sl@8: iFrameCurrent(NULL), sl@8: iFramePrevious(NULL), sl@8: iFrameAlpha(NULL), sl@8: iFrameBeta(NULL), sl@8: iFrameGamma(NULL), sl@12: iNeedFullFrameUpdate(0) sl@8: { sl@8: //ResetBuffers(); sl@8: } sl@8: sl@8: /** sl@8: */ sl@8: GP1212A01A::~GP1212A01A() sl@8: { sl@8: delete iFrameAlpha; sl@8: iFrameAlpha=NULL; sl@8: // sl@8: delete iFrameBeta; sl@8: iFrameBeta=NULL; sl@8: // sl@8: delete iFrameGamma; sl@8: iFrameGamma=NULL; sl@8: // sl@8: iFrameNext=NULL; sl@8: iFrameCurrent=NULL; sl@8: iFramePrevious=NULL; sl@8: // sl@8: iNeedFullFrameUpdate=0; sl@8: } sl@8: sl@8: /** sl@8: */ sl@8: int GP1212A01A::Open() sl@8: { sl@8: int success = HidDevice::Open(KFutabaVendorId,KFutabaProductIdGP1212A01A,NULL); sl@8: if (success) sl@8: { sl@8: //Allocate both frames sl@8: delete iFrameAlpha; sl@8: iFrameAlpha=NULL; sl@13: iFrameAlpha=new BitArrayHigh(KGP12xFrameBufferPixelCount); sl@8: // sl@8: delete iFrameBeta; sl@8: iFrameBeta=NULL; sl@13: iFrameBeta=new BitArrayHigh(KGP12xFrameBufferPixelCount); sl@8: // sl@8: delete iFrameGamma; sl@8: iFrameGamma=NULL; sl@13: iFrameGamma=new BitArrayHigh(KGP12xFrameBufferPixelCount); sl@8: // sl@8: iFrameNext=iFrameAlpha; sl@8: iFrameCurrent=iFrameBeta; sl@8: iFramePrevious=iFrameGamma; sl@8: sl@8: sl@8: //To make sure it is synced properly sl@8: iNeedFullFrameUpdate=0; sl@8: // sl@8: SetNonBlocking(1); sl@8: //Since we can't get our display position we force it to our default sl@8: //This makes sure frames are in sync from the start sl@8: //Clever clients will have taken care of putting back frame (0,0) before closing sl@8: SetDisplayPosition(iDisplayPositionX,iDisplayPositionY); sl@8: } sl@8: return success; sl@8: } sl@8: sl@8: /** sl@8: */ sl@22: void GP1212A01A::SetPixel(unsigned char aX, unsigned char aY, unsigned int aPixel) sl@8: { sl@8: // sl@8: //int byteOffset=(aX*HeightInPixels()+aY)/8; sl@8: //int bitOffset=(aX*HeightInPixels()+aY)%8; sl@8: //iNextFrame[byteOffset] |= ( (aOn?0x01:0x00) << bitOffset ); sl@8: sl@22: //Pixel is on if any of the non-alpha component is not null sl@22: bool on = (aPixel&0x00FFFFFF)!=0x00000000; sl@22: sl@8: if (iOffScreenMode) sl@8: { sl@22: if (on) sl@8: { sl@8: iFrameNext->SetBit(aX*HeightInPixels()+aY); sl@8: } sl@8: else sl@8: { sl@8: iFrameNext->ClearBit(aX*HeightInPixels()+aY); sl@8: } sl@8: } sl@8: else sl@8: { sl@8: //Just specify a one pixel block sl@22: SetPixelBlock(aX,aY,0x00,0x01,on); sl@8: } sl@8: } sl@8: sl@8: /** sl@8: */ sl@13: /* sl@8: void GP1212A01A::BitBlit(const BitArray& aBitmap, int aSrcWidth, int aSrcHeight, int aTargetX, int aTargetY) const sl@8: { sl@8: //TODO: amend loop values so that we don't keep on looping past our frame buffer dimensions. sl@8: for (int i=0;iSetBitValue((aTargetX+i)*HeightInPixels()+aTargetY+j,aBitmap[+i*aSrcHeight+j]); sl@8: } sl@8: } sl@8: } sl@13: */ sl@8: sl@8: /** sl@8: Clear our client side back buffer. sl@8: Call to SwapBuffers must follow to actually clear the display. sl@8: */ sl@8: void GP1212A01A::Clear() sl@8: { sl@8: //memset(iNextFrame->Ptr(),0x00,FrameBufferSizeInBytes()); sl@8: if (iOffScreenMode) sl@8: { sl@8: iFrameNext->ClearAll(); sl@8: } sl@8: else sl@8: { sl@8: SendClearCommand(); sl@8: } sl@8: } sl@8: sl@8: /** sl@10: Turn on all pixels. sl@10: Must be followed by a SwapBuffers call. sl@10: */ sl@10: void GP1212A01A::Fill() sl@10: { sl@10: SetAllPixels(0xFF); sl@10: } sl@10: sl@10: /** sl@8: Set all pixels on our screen to the desired value. sl@8: This operation is performed off screen to avoid tearing. sl@8: @param 8 pixels pattern sl@8: */ sl@8: void GP1212A01A::SetAllPixels(unsigned char aPattern) sl@8: { sl@8: //With a single buffer sl@8: //unsigned char screen[2048]; //One screen worth of pixels sl@8: //memset(screen,0xFF,sizeof(screen)); sl@8: //SetPixelBlock(0,0,63,sizeof(screen),screen); sl@8: sl@8: sl@8: if (iOffScreenMode) sl@8: { sl@8: memset(iFrameNext->Ptr(),aPattern,FrameBufferSizeInBytes()); sl@8: } sl@8: else sl@8: { sl@8: //Using pattern SetPixelBlock variant. sl@8: SetPixelBlock(0,0,63,FrameBufferSizeInBytes(),aPattern); sl@8: } sl@8: // sl@8: } sl@8: sl@8: sl@8: /** sl@8: Set the defined pixel block to the given value. sl@8: @param X coordinate of our pixel block starting point. sl@8: @param Y coordinate of our pixel block starting point. sl@8: @param The height of our pixel block. sl@8: @param The size of our pixel data. Number of pixels divided by 8. sl@8: @param The value set to 8 pixels used as a pattern. sl@8: */ sl@8: void GP1212A01A::SetPixelBlock(unsigned char aX, unsigned char aY, int aHeight, int aSize, unsigned char aValue) sl@8: { sl@8: OffScreenTranslation(aX,aY); sl@8: FutabaVfdReport report; sl@8: report[0]=0x00; //Report ID sl@8: report[1]=(aSize<=report.Size()-10?aSize+0x08:64); //Report length. -10 is for our header first 10 bytes. +8 is for our Futaba header size sl@8: report[2]=0x1B; //Command ID sl@8: report[3]=0x5B; //Command ID sl@8: report[4]=0xF0; //Command ID sl@8: report[5]=aX; //X sl@8: report[6]=aY; //Y sl@8: report[7]=aHeight; //Y length before return. Though outside the specs, setting this to zero apparently allows us to modify a single pixel without touching any other. sl@8: report[8]=aSize>>8; //Size of pixel data in bytes (MSB) sl@8: report[9]=aSize; //Size of pixel data in bytes (LSB) sl@8: int sizeWritten=MIN(aSize,report.Size()-10); sl@8: memset(report.Buffer()+10, aValue, sizeWritten); sl@8: Write(report); sl@8: sl@8: int remainingSize=aSize; sl@8: //We need to keep on sending our pixel data until we are done sl@8: while (report[1]==64) sl@8: { sl@8: report.Reset(); sl@8: remainingSize-=sizeWritten; sl@8: report[0]=0x00; //Report ID sl@8: report[1]=(remainingSize<=report.Size()-2?remainingSize:64); //Report length, should be 64 or the remaining size sl@8: sizeWritten=(report[1]==64?63:report[1]); sl@8: memset(report.Buffer()+2, aValue, sizeWritten); sl@8: Write(report); sl@8: } sl@8: } sl@8: sl@8: /** sl@8: Set the defined pixel block to the given value. sl@8: @param X coordinate of our pixel block starting point. sl@8: @param Y coordinate of our pixel block starting point. sl@8: @param The height of our pixel block. sl@8: @param The size of our pixel data. Number of pixels divided by 8. sl@8: @param Pointer to our pixel data. sl@8: */ sl@8: void GP1212A01A::SetPixelBlock(unsigned char aX, unsigned char aY, int aHeight, int aSize, unsigned char* aPixels) sl@8: { sl@8: OffScreenTranslation(aX,aY); sl@8: FutabaVfdReport report; sl@8: report[0]=0x00; //Report ID sl@8: report[1]=(aSize<=report.Size()-10?aSize+0x08:64); //Report length. -10 is for our header first 10 bytes. +8 is for our Futaba header size sl@8: report[2]=0x1B; //Command ID sl@8: report[3]=0x5B; //Command ID sl@8: report[4]=0xF0; //Command ID sl@8: report[5]=aX; //X sl@8: report[6]=aY; //Y sl@8: report[7]=aHeight; //Y length before return. Though outside the specs, setting this to zero apparently allows us to modify a single pixel without touching any other. sl@8: report[8]=aSize>>8; //Size of pixel data in bytes (MSB) sl@8: report[9]=aSize; //Size of pixel data in bytes (LSB) sl@8: int sizeWritten=MIN(aSize,report.Size()-10); sl@8: memcpy(report.Buffer()+10, aPixels, sizeWritten); sl@8: Write(report); sl@8: sl@8: int remainingSize=aSize; sl@8: //We need to keep on sending our pixel data until we are done sl@8: while (report[1]==64) sl@8: { sl@8: report.Reset(); sl@8: remainingSize-=sizeWritten; sl@8: report[0]=0x00; //Report ID sl@8: report[1]=(remainingSize<=report.Size()-2?remainingSize:64); //Report length, should be 64 or the remaining size sl@8: sizeWritten=(report[1]==64?63:report[1]); sl@8: memcpy(report.Buffer()+2, aPixels+(aSize-remainingSize), sizeWritten); sl@8: Write(report); sl@8: } sl@8: } sl@8: sl@8: /** sl@8: Using this function is advised against as is causes tearing. sl@8: Use Clear instead. sl@8: */ sl@8: void GP1212A01A::SendClearCommand() sl@8: { sl@8: //1BH,5BH,32H,4AH sl@8: //Send Clear Display Command sl@8: FutabaVfdReport report; sl@8: report[0]=0x00; //Report ID sl@8: report[1]=0x04; //Report length sl@8: report[2]=0x1B; //Command ID sl@8: report[3]=0x5B; //Command ID sl@8: report[4]=0x32; //Command ID sl@8: report[5]=0x4A; //Command ID sl@8: Write(report); sl@8: } sl@8: sl@8: /** sl@8: Change our display position within our buffer. sl@8: */ sl@8: void GP1212A01A::SetDisplayPosition(DW aDw,unsigned char aX, unsigned char aY) sl@8: { sl@8: //1BH,5BH,Dw,Px,Py sl@8: //Send Display Position Settings Command sl@8: FutabaVfdReport report; sl@8: report[0]=0x00; //Report ID sl@8: report[1]=0x05; //Report length sl@8: report[2]=0x1B; //Command ID sl@8: report[3]=0x5B; //Command ID sl@8: report[4]=aDw; //Specify our DW sl@8: report[5]=aX; //X coordinate of our DW top-left corner sl@8: report[6]=aY; //Y coordinate of our DW top-left corner sl@8: Write(report); sl@8: } sl@8: sl@8: /** sl@8: Change our display position within our buffer. sl@8: */ sl@8: void GP1212A01A::SetDisplayPosition(unsigned char aX, unsigned char aY) sl@8: { sl@8: //Specs apparently says both DW should remain the same sl@8: //Just don't ask sl@8: SetDisplayPosition(GP1212A01A::DW1,aX,aY); sl@8: SetDisplayPosition(GP1212A01A::DW2,aX,aY); sl@8: iDisplayPositionX=aX; sl@8: iDisplayPositionY=aY; sl@8: } sl@8: sl@8: /** sl@8: Provide Y coordinate of our off screen buffer. sl@8: */ sl@8: unsigned char GP1212A01A::OffScreenY() const sl@8: { sl@8: //Overflowing is fine this is just what we want sl@8: return iDisplayPositionY+HeightInPixels(); sl@8: } sl@8: sl@8: /** sl@8: Put our off screen buffer on screen. sl@8: On screen buffer goes off screen. sl@8: */ sl@8: void GP1212A01A::SwapBuffers() sl@8: { sl@8: //Only perform buffer swapping if off screen mode is enabled sl@8: if (OffScreenMode()) sl@8: { sl@8: //Send host back buffer to device back buffer sl@8: if (!iUseFrameDifferencing || iNeedFullFrameUpdatePtr()); sl@8: } sl@8: else sl@8: { sl@8: //Frame diff algo is enabled sl@8: //We are going to send to our device only the differences between next frame and previous frame sl@8: SendModifiedPixelBlocks(); sl@8: } sl@8: //Swap device front and back buffer sl@8: SetDisplayPosition(iDisplayPositionX,OffScreenY()); sl@8: sl@8: //Cycle through our frame buffers sl@8: //We keep track of previous frame which is in fact our device back buffer. sl@8: //We can then compare previous and next frame and send only the differences to our device. sl@8: //This mechanism allows us to reduce traffic over our USB bus thus improving our frame rate from 14 FPS to 30 FPS. sl@8: //Keep our previous frame pointer sl@13: BitArrayHigh* previousFrame=iFramePrevious; sl@8: //Current frame becomes the previous one sl@8: iFramePrevious = iFrameCurrent; sl@8: //Next frame becomes the current one sl@8: iFrameCurrent = iFrameNext; sl@8: //Next frame is now our former previous sl@8: iFrameNext = previousFrame; sl@8: } sl@8: } sl@8: sl@8: sl@8: //Define the edge of our pixel block sl@8: //Pixel blocks of 32x32 seems to run almost as fast as full screen update in worse case scenarii. sl@8: //Though I wonder if in some situations 16 could be better. Make this an attribute at some point if need be. sl@8: const int KPixelBlockEdge = 32; sl@8: const int KPixelBlockSizeInBits = KPixelBlockEdge*KPixelBlockEdge; sl@8: const int KPixelBlockSizeInBytes = KPixelBlockSizeInBits/8; sl@8: sl@8: sl@8: /** sl@8: * @brief GP1212A01A::SendModifiedPixelBlocks sl@8: * Compare our back and front buffer and send to the device only the modified pixels. sl@8: */ sl@8: void GP1212A01A::SendModifiedPixelBlocks() sl@8: { sl@8: int w=WidthInPixels(); sl@8: int h=HeightInPixels(); sl@8: sl@8: sl@8: //TODO: optimize with memcmp and 16 inc sl@8: /* sl@8: sl@8: for (int i=0;iPtr()+offset); sl@8: } sl@8: } sl@8: } sl@8: */ sl@8: sl@13: BitArrayHigh nextBlock(KPixelBlockSizeInBits); sl@13: BitArrayHigh previousBlock(KPixelBlockSizeInBits); sl@8: sl@8: for (int i=0;iPtr()+offset,iFramePrevious->Ptr()+offset,32 )) //32=(16*16/8) sl@8: if (memcmp(nextBlock.Ptr(),previousBlock.Ptr(),KPixelBlockSizeInBytes)!=0) sl@8: { sl@8: //We need to update that block sl@8: SetPixelBlock(i,j,KPixelBlockEdge-1,KPixelBlockSizeInBytes,nextBlock.Ptr()); sl@8: //SetPixelBlock(i,j,15,32,0xFF/*nextBlock.Ptr()*/); sl@8: //SetDisplayPosition(iDisplayPositionX,OffScreenY()); sl@8: //SetDisplayPosition(iDisplayPositionX,OffScreenY()); sl@8: sl@8: //SetPixelBlock(i,j,15,32,iFrameNext->Ptr()+offset); sl@8: } sl@8: } sl@8: } sl@8: sl@8: } sl@8: sl@8: /** sl@8: Translate the given pixel coordinate according to our off screen mode. sl@8: */ sl@8: void GP1212A01A::OffScreenTranslation(unsigned char& aX, unsigned char& aY) sl@8: { sl@8: if (OffScreenMode()) sl@8: { sl@8: aX+=WidthInPixels()-iDisplayPositionX; sl@8: aY+=HeightInPixels()-iDisplayPositionY; sl@8: } sl@8: } sl@8: sl@8: sl@8: /** sl@8: */ sl@8: void GP1212A01A::ResetBuffers() sl@8: { sl@8: //iNextFrame->ClearAll(); sl@8: //memset(iFrameAlpha,0x00,sizeof(iFrameAlpha)); sl@8: //memset(iFrameBeta,0x00,sizeof(iFrameBeta)); sl@8: } sl@8: sl@11: sl@11: /** sl@11: */ sl@11: void GP1212A01A::Request(TMiniDisplayRequest aRequest) sl@11: { sl@11: switch (aRequest) sl@11: { sl@11: case EMiniDisplayRequestDeviceId: sl@11: RequestDeviceId(); sl@11: break; sl@11: case EMiniDisplayRequestFirmwareRevision: sl@11: RequestFirmwareRevision(); sl@11: break; sl@11: case EMiniDisplayRequestPowerSupplyStatus: sl@11: RequestPowerSupplyStatus(); sl@11: break; sl@11: default: sl@11: //Not supported sl@11: break; sl@11: }; sl@11: } sl@11: sl@8: /** sl@8: */ sl@8: void GP1212A01A::RequestDeviceId() sl@8: { sl@8: if (RequestPending()) sl@8: { sl@8: //Abort silently for now sl@8: return; sl@8: } sl@8: sl@8: //1BH,5BH,63H,49H,44H sl@8: //Send Read ID command sl@8: FutabaVfdReport report; sl@8: report[0]=0x00; //Report ID sl@8: report[1]=0x05; //Report length sl@8: report[2]=0x1B; //Command ID sl@8: report[3]=0x5B; //Command ID sl@8: report[4]=0x63; //Command ID sl@8: report[5]=0x49; //Command ID sl@8: report[6]=0x44; //Command ID sl@8: if (Write(report)==report.Size()) sl@8: { sl@11: SetRequest(EMiniDisplayRequestDeviceId); sl@8: } sl@8: } sl@8: sl@8: /** sl@8: */ sl@8: void GP1212A01A::RequestFirmwareRevision() sl@8: { sl@8: if (RequestPending()) sl@8: { sl@8: //Abort silently for now sl@8: return; sl@8: } sl@8: sl@8: //1BH,5BH,63H,46H,52H sl@8: //Send Software Revision Read Command sl@8: FutabaVfdReport report; sl@8: report[0]=0x00; //Report ID sl@8: report[1]=0x05; //Report length sl@8: report[2]=0x1B; //Command ID sl@8: report[3]=0x5B; //Command ID sl@8: report[4]=0x63; //Command ID sl@8: report[5]=0x46; //Command ID sl@8: report[6]=0x52; //Command ID sl@8: if (Write(report)==report.Size()) sl@8: { sl@11: SetRequest(EMiniDisplayRequestFirmwareRevision); sl@8: } sl@8: } sl@8: sl@8: /** sl@8: */ sl@8: void GP1212A01A::RequestPowerSupplyStatus() sl@8: { sl@8: if (RequestPending()) sl@8: { sl@8: //Abort silently for now sl@8: return; sl@8: } sl@8: //1BH,5BH,63H,50H,4DH sl@8: //Send Power Suppply Monitor Command sl@8: FutabaVfdReport report; sl@8: report[0]=0x00; //Report ID sl@8: report[1]=0x05; //Report length sl@8: report[2]=0x1B; //Command ID sl@8: report[3]=0x5B; //Command ID sl@8: report[4]=0x63; //Command ID sl@8: report[5]=0x50; //Command ID sl@8: report[6]=0x4D; //Command ID sl@8: if (Write(report)==report.Size()) sl@8: { sl@11: SetRequest(EMiniDisplayRequestPowerSupplyStatus); sl@8: } sl@8: } sl@8: sl@8: sl@8: /** sl@8: This is for development purposes only. sl@8: Production application should stick to off-screen mode to avoid tearing. sl@8: */ sl@8: void GP1212A01A::ToggleOffScreenMode() sl@8: { sl@8: SetOffScreenMode(!iOffScreenMode); sl@8: } sl@8: sl@8: /** sl@8: * @brief GP1212A01A::SetOffScreenMode sl@8: * @param aOn sl@8: * @return sl@8: */ sl@8: void GP1212A01A::SetOffScreenMode(bool aOn) sl@8: { sl@8: if (aOn==iOffScreenMode) sl@8: { sl@8: //Nothing to do here sl@8: return; sl@8: } sl@8: sl@8: iOffScreenMode=aOn; sl@8: sl@8: //Clean up our buffers upon switching modes sl@8: SetDisplayPosition(0,0); sl@8: Clear(); sl@8: SwapBuffers(); sl@8: Clear(); sl@8: } sl@8: sl@8: /** sl@8: Tries to complete our current request if we have one pending. sl@8: */ sl@8: TMiniDisplayRequest GP1212A01A::AttemptRequestCompletion() sl@8: { sl@8: if (!RequestPending()) sl@8: { sl@8: return EMiniDisplayRequestNone; sl@8: } sl@8: sl@8: int res=Read(iInputReport); sl@8: sl@8: if (!res) sl@8: { sl@8: return EMiniDisplayRequestNone; sl@8: } sl@8: sl@8: //Process our request sl@8: if (CurrentRequest()==EMiniDisplayRequestPowerSupplyStatus) sl@8: { sl@8: if (iInputReport[1]==0x4F && iInputReport[2]==0x4E) sl@8: { sl@8: iPowerOn = true; sl@8: } sl@8: else if (iInputReport[1]==0x4F && iInputReport[2]==0x46 && iInputReport[3]==0x46) sl@8: { sl@8: iPowerOn = false; sl@8: } sl@8: } sl@8: else if (CurrentRequest()==EMiniDisplayRequestDeviceId) sl@8: { sl@8: unsigned char* ptr=&iInputReport[1]; sl@8: strcpy(iDeviceId,(const char*)ptr); sl@8: } sl@8: else if (CurrentRequest()==EMiniDisplayRequestFirmwareRevision) sl@8: { sl@8: unsigned char* ptr=&iInputReport[1]; sl@8: strcpy(iFirmwareRevision,(const char*)ptr); sl@8: } sl@8: sl@11: TMiniDisplayRequest completed=CurrentRequest(); sl@8: //Our request was completed sl@11: SetRequest(EMiniDisplayRequestNone); sl@8: sl@8: return completed; sl@8: } sl@8: sl@8: sl@8: /** sl@8: Set our screen brightness. sl@8: @param The desired brightness level. Must be between MinBrightness and MaxBrightness. sl@8: */ sl@8: void GP1212A01A::SetBrightness(int aBrightness) sl@8: { sl@8: if (aBrightnessMaxBrightness()) sl@8: { sl@8: //Brightness out of range. sl@8: //Just ignore that request. sl@8: return; sl@8: } sl@8: sl@8: FutabaVfdReport report; sl@8: report[0]=0x00; //Report ID sl@8: report[1]=0x06; //Report size sl@8: report[2]=0x1B; //Command ID sl@8: report[3]=0x5C; //Command ID sl@8: report[4]=0x3F; //Command ID sl@8: report[5]=0x4C; //Command ID sl@8: report[6]=0x44; //Command ID sl@8: report[7]=0x30+aBrightness; //Brightness level sl@8: Write(report); sl@8: } sl@8: sl@8: