MiniDisplay/FutabaVfd.cpp
author sl
Fri, 30 May 2014 13:45:14 +0200
changeset 21 434d6b8a406d
parent 20 cd6d76b9b47e
child 22 ca9e48af31e6
permissions -rw-r--r--
Frame diff algo now working nicely and providing twice the frame rate.
     1 
     2 #include "FutabaVfd.h"
     3 //#include <stdlib.h>
     4 #include <string.h>
     5 
     6 
     7 #ifdef DEBUG_FRAME_DIFF
     8 #include <QImage>
     9 #include <QTextStream>
    10 #endif
    11 
    12 //
    13 //
    14 //
    15 
    16 
    17 
    18 
    19 
    20 //
    21 //
    22 //
    23 
    24 FutabaVfdCommand::FutabaVfdCommand():/*iBuffer(NULL),*/iSize(0),iMaxSize(0)
    25     {
    26     }
    27 
    28 FutabaVfdCommand::~FutabaVfdCommand()
    29     {
    30     //Delete();
    31     }
    32 
    33 
    34 /**
    35 
    36 */
    37 void FutabaVfdCommand::Reset()
    38     {
    39     memset(iReports,0,sizeof(iReports));
    40     }
    41 
    42 
    43 
    44 /**
    45 
    46 */
    47 /*
    48 void FutabaVfdCommand::Create(int aMaxSize)
    49     {
    50     iBuffer=new unsigned char[aMaxSize];
    51     if (iBuffer)
    52         {
    53         iMaxSize = aMaxSize;
    54         iSize = 0;
    55         }
    56     }
    57 */
    58 
    59 /**
    60 
    61 */
    62 /*
    63 void FutabaVfdCommand::Delete()
    64 {
    65     delete[] iBuffer;
    66     iBuffer = NULL;
    67     iMaxSize = 0;
    68     iSize = 0;
    69 }
    70 */
    71 
    72 
    73 
    74 
    75 //
    76 // class GP1212A01A
    77 //
    78 
    79 GP1212A01A::GP1212A01A():
    80 	iDisplayPositionX(0),iDisplayPositionY(0),
    81     iOffScreenMode(true),
    82     iFrameNext(NULL),
    83     iFrameCurrent(NULL),
    84     iFramePrevious(NULL),
    85     iFrameAlpha(NULL),
    86     iFrameBeta(NULL),
    87     iFrameGamma(NULL),
    88     iNeedFullFrameUpdate(0),
    89     iRequest(ERequestNone),iPowerOn(false)
    90 	{
    91 	//ResetBuffers();
    92 	}
    93 
    94 /**
    95 */
    96 GP1212A01A::~GP1212A01A()
    97 	{
    98     delete iFrameAlpha;
    99     iFrameAlpha=NULL;
   100     //
   101     delete iFrameBeta;
   102     iFrameBeta=NULL;
   103     //
   104     delete iFrameGamma;
   105     iFrameGamma=NULL;
   106     //
   107     iFrameNext=NULL;
   108     iFrameCurrent=NULL;
   109     iFramePrevious=NULL;
   110     //
   111     iNeedFullFrameUpdate=0;
   112 	}
   113 
   114 /**
   115 */
   116 int GP1212A01A::Open()
   117 	{
   118 	int success = HidDevice::Open(KFutabaVendorId,KFutabaProductIdGP1212A01A,NULL);
   119 	if (success)
   120 		{
   121         //Allocate both frames
   122         delete iFrameAlpha;
   123         iFrameAlpha=NULL;
   124         iFrameAlpha=new BitArray(KGP12xFrameBufferPixelCount);
   125         //
   126         delete iFrameBeta;
   127         iFrameBeta=NULL;
   128         iFrameBeta=new BitArray(KGP12xFrameBufferPixelCount);
   129         //
   130         delete iFrameGamma;
   131         iFrameGamma=NULL;
   132         iFrameGamma=new BitArray(KGP12xFrameBufferPixelCount);
   133         //
   134         iFrameNext=iFrameAlpha;
   135         iFrameCurrent=iFrameBeta;
   136         iFramePrevious=iFrameGamma;
   137 
   138 
   139         //To make sure it is synced properly
   140         iNeedFullFrameUpdate=0;
   141         //
   142 		SetNonBlocking(1);
   143         //Since we can't get our display position we force it to our default
   144 		//This makes sure frames are in sync from the start
   145         //Clever clients will have taken care of putting back frame (0,0) before closing
   146 		SetDisplayPosition(iDisplayPositionX,iDisplayPositionY);
   147 		}
   148 	return success;
   149 	}
   150 
   151 /**
   152 */
   153 void GP1212A01A::SetPixel(unsigned char aX, unsigned char aY, bool aOn)
   154 	{
   155 	//
   156 	//int byteOffset=(aX*HeightInPixels()+aY)/8;
   157 	//int bitOffset=(aX*HeightInPixels()+aY)%8;
   158     //iNextFrame[byteOffset] |= ( (aOn?0x01:0x00) << bitOffset );
   159 
   160     if (iOffScreenMode)
   161         {
   162         if (aOn)
   163             {
   164             iFrameNext->SetBit(aX*HeightInPixels()+aY);
   165             }
   166         else
   167             {
   168             iFrameNext->ClearBit(aX*HeightInPixels()+aY);
   169             }
   170         }
   171     else
   172         {
   173         //Just specify a one pixel block
   174         SetPixelBlock(aX,aY,0x00,0x01,aOn);
   175         }
   176 	}
   177 
   178 /**
   179 */
   180 void GP1212A01A::BitBlit(const BitArray& aBitmap, int aSrcWidth, int aSrcHeight, int aTargetX, int aTargetY) const
   181 	{
   182 	//TODO: amend loop values so that we don't keep on looping past our frame buffer dimensions.
   183 	for (int i=0;i<aSrcWidth;i++)
   184 		{
   185 		for (int j=0;j<aSrcHeight;j++)
   186 			{
   187             iFrameNext->SetBitValue((aTargetX+i)*HeightInPixels()+aTargetY+j,aBitmap[+i*aSrcHeight+j]);
   188 			}
   189 		}
   190 	}
   191 
   192 /**
   193 Clear our client side back buffer.
   194 Call to SwapBuffers must follow to actually clear the display.
   195 */
   196 void GP1212A01A::Clear()
   197     {
   198     //memset(iNextFrame->Ptr(),0x00,FrameBufferSizeInBytes());
   199     if (iOffScreenMode)
   200         {
   201         iFrameNext->ClearAll();
   202         }
   203     else
   204         {
   205         SendClearCommand();
   206         }
   207     }
   208 
   209 /**
   210 Set all pixels on our screen to the desired value.
   211 This operation is performed off screen to avoid tearing.
   212 @param 8 pixels pattern
   213 */
   214 void GP1212A01A::SetAllPixels(unsigned char aPattern)
   215 	{
   216 	//With a single buffer
   217 	//unsigned char screen[2048]; //One screen worth of pixels
   218 	//memset(screen,0xFF,sizeof(screen));
   219 	//SetPixelBlock(0,0,63,sizeof(screen),screen);
   220 
   221 
   222     if (iOffScreenMode)
   223         {
   224         memset(iFrameNext->Ptr(),aPattern,FrameBufferSizeInBytes());
   225         }
   226     else
   227         {
   228         //Using pattern SetPixelBlock variant.
   229         SetPixelBlock(0,0,63,FrameBufferSizeInBytes(),aPattern);
   230         }
   231 	//
   232 	}
   233 
   234 
   235 /**
   236 Set the defined pixel block to the given value.
   237 @param X coordinate of our pixel block starting point.
   238 @param Y coordinate of our pixel block starting point.
   239 @param The height of our pixel block.
   240 @param The size of our pixel data. Number of pixels divided by 8.
   241 @param The value set to 8 pixels used as a pattern.
   242 */
   243 void GP1212A01A::SetPixelBlock(unsigned char aX, unsigned char aY, int aHeight, int aSize, unsigned char aValue)
   244 	{
   245 	OffScreenTranslation(aX,aY);
   246     FutabaVfdReport report;
   247     report[0]=0x00; //Report ID
   248     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
   249     report[2]=0x1B; //Command ID
   250     report[3]=0x5B; //Command ID
   251     report[4]=0xF0; //Command ID
   252     report[5]=aX;   //X
   253     report[6]=aY;   //Y
   254     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.
   255 	report[8]=aSize>>8; //Size of pixel data in bytes (MSB)
   256 	report[9]=aSize;	//Size of pixel data in bytes (LSB)
   257     int sizeWritten=MIN(aSize,report.Size()-10);
   258     memset(report.Buffer()+10, aValue, sizeWritten);
   259     Write(report);
   260 
   261     int remainingSize=aSize;
   262     //We need to keep on sending our pixel data until we are done
   263     while (report[1]==64)
   264         {
   265         report.Reset();
   266         remainingSize-=sizeWritten;
   267         report[0]=0x00; //Report ID
   268         report[1]=(remainingSize<=report.Size()-2?remainingSize:64); //Report length, should be 64 or the remaining size
   269         sizeWritten=(report[1]==64?63:report[1]);
   270         memset(report.Buffer()+2, aValue, sizeWritten);
   271         Write(report);
   272         }
   273 	}
   274 
   275 /**
   276 Set the defined pixel block to the given value.
   277 @param X coordinate of our pixel block starting point.
   278 @param Y coordinate of our pixel block starting point.
   279 @param The height of our pixel block.
   280 @param The size of our pixel data. Number of pixels divided by 8.
   281 @param Pointer to our pixel data.
   282 */
   283 void GP1212A01A::SetPixelBlock(unsigned char aX, unsigned char aY, int aHeight, int aSize, unsigned char* aPixels)
   284     {
   285 	OffScreenTranslation(aX,aY);
   286     FutabaVfdReport report;
   287     report[0]=0x00; //Report ID
   288     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
   289     report[2]=0x1B; //Command ID
   290     report[3]=0x5B; //Command ID
   291     report[4]=0xF0; //Command ID
   292     report[5]=aX;   //X
   293     report[6]=aY;   //Y
   294     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.
   295 	report[8]=aSize>>8; //Size of pixel data in bytes (MSB)
   296 	report[9]=aSize;	//Size of pixel data in bytes (LSB)
   297     int sizeWritten=MIN(aSize,report.Size()-10);
   298     memcpy(report.Buffer()+10, aPixels, sizeWritten);
   299     Write(report);
   300 
   301     int remainingSize=aSize;
   302     //We need to keep on sending our pixel data until we are done
   303     while (report[1]==64)
   304         {
   305         report.Reset();
   306         remainingSize-=sizeWritten;
   307         report[0]=0x00; //Report ID
   308         report[1]=(remainingSize<=report.Size()-2?remainingSize:64); //Report length, should be 64 or the remaining size
   309         sizeWritten=(report[1]==64?63:report[1]);
   310         memcpy(report.Buffer()+2, aPixels+(aSize-remainingSize), sizeWritten);
   311         Write(report);
   312         }
   313     }
   314 
   315 /**
   316 Using this function is advised against as is causes tearing.
   317 Use Clear instead.
   318 */
   319 void GP1212A01A::SendClearCommand()
   320 	{
   321     //1BH,5BH,32H,4AH
   322     //Send Clear Display Command
   323 	FutabaVfdReport report;
   324 	report[0]=0x00; //Report ID
   325 	report[1]=0x04; //Report length
   326 	report[2]=0x1B; //Command ID
   327 	report[3]=0x5B; //Command ID
   328 	report[4]=0x32; //Command ID
   329 	report[5]=0x4A; //Command ID
   330 	Write(report);
   331 	}
   332 
   333 /**
   334 Change our display position within our buffer.
   335 */
   336 void GP1212A01A::SetDisplayPosition(DW aDw,unsigned char aX, unsigned char aY)
   337     {
   338     //1BH,5BH,Dw,Px,Py
   339     //Send Display Position Settings Command
   340     FutabaVfdReport report;
   341     report[0]=0x00; //Report ID
   342     report[1]=0x05; //Report length
   343     report[2]=0x1B; //Command ID
   344     report[3]=0x5B; //Command ID
   345     report[4]=aDw;  //Specify our DW
   346     report[5]=aX;   //X coordinate of our DW top-left corner
   347     report[6]=aY;   //Y coordinate of our DW top-left corner
   348     Write(report);
   349     }
   350 
   351 /**
   352 Change our display position within our buffer.
   353 */
   354 void GP1212A01A::SetDisplayPosition(unsigned char aX, unsigned char aY)
   355 	{
   356 	//Specs apparently says both DW should remain the same
   357 	//Just don't ask
   358     SetDisplayPosition(GP1212A01A::DW1,aX,aY);
   359     SetDisplayPosition(GP1212A01A::DW2,aX,aY);
   360 	iDisplayPositionX=aX;
   361 	iDisplayPositionY=aY;
   362 	}
   363 
   364 /**
   365 Provide Y coordinate of our off screen buffer.
   366 */
   367 unsigned char GP1212A01A::OffScreenY() const
   368 	{
   369 	//Overflowing is fine this is just what we want
   370 	return iDisplayPositionY+HeightInPixels();
   371 	}
   372 
   373 /**
   374 Put our off screen buffer on screen.
   375 On screen buffer goes off screen.
   376 */
   377 void GP1212A01A::SwapBuffers()
   378 	{
   379 	//Only perform buffer swapping if off screen mode is enabled
   380 	if (OffScreenMode())
   381 		{
   382 		//Send host back buffer to device back buffer
   383         if (iNeedFullFrameUpdate<3)
   384             {
   385             //TODO: enable this once SendModifiedPixelBlocks works
   386             iNeedFullFrameUpdate++;
   387             SetPixelBlock(0,0,63,FrameBufferSizeInBytes(),iFrameNext->Ptr());
   388             }
   389         else
   390             {
   391             SendModifiedPixelBlocks();
   392             }
   393 		//Swap device front and back buffer
   394 		SetDisplayPosition(iDisplayPositionX,OffScreenY());
   395 
   396         //Cycle through our frame buffers
   397         //We keep track of previous frame which is in fact our device back buffer.
   398         //We can then compare previous and next frame and send only the differences to our device.
   399         //This mechanism allows us to reduce traffic over our USB bus thus improving our frame rate from 14 FPS to 30 FPS.
   400         //Keep our previous frame pointer
   401         BitArray* previousFrame=iFramePrevious;
   402         //Current frame becomes the previous one
   403         iFramePrevious = iFrameCurrent;
   404         //Next frame becomes the current one
   405         iFrameCurrent = iFrameNext;
   406         //Next frame is now our former previous
   407         iFrameNext = previousFrame;
   408 		}
   409 	}
   410 
   411 
   412 /**
   413  * @brief GP1212A01A::SendModifiedPixelBlocks
   414  * Compare our back and front buffer and send to the device only the modified pixels.
   415  * TODO: Get this working at some point.
   416  */
   417 void GP1212A01A::SendModifiedPixelBlocks()
   418     {
   419     //The largest pixel block we can sanely send with one report is 16*16
   420     //const int KBlocksPerRow = WidthInPixels()/16; //16
   421     //const int KBlocksPerColumn = HeightInPixels()/16; //4
   422 
   423     int w=WidthInPixels();
   424     int h=HeightInPixels();
   425 
   426 
   427 
   428     //TODO: optimize with memcmp and 16 inc
   429     /*
   430     for (int i=0;i<w;i++)
   431         {
   432         for (int j=0;j<h;j++)
   433             {
   434             //aX*HeightInPixels()+aY
   435             if ((*iFrameNext)[i*h+j]!=(*iFramePrevious)[i*h+j])
   436                 {
   437                 //We need to update that pixel
   438                 SetPixelBlock(i,j,0,1,((*iFrameNext)[i*h+j]?0x01:0x00));
   439                 //SetDisplayPosition(iDisplayPositionX,OffScreenY());
   440                 //SetDisplayPosition(iDisplayPositionX,OffScreenY());
   441 
   442                 //SetPixelBlock(i,j,15,32,iNextFrame->Ptr()+offset);
   443                 }
   444             }
   445         }
   446     */
   447 
   448     BitArray nextBlock(16*16);
   449     BitArray previousBlock(16*16);
   450 
   451     for (int i=0;i<w;i+=16)
   452         {
   453         for (int j=0;j<h;j+=16)
   454             {
   455             //aX*HeightInPixels()+aY
   456             //int offset=(i*w/8)+(j/8);
   457 
   458 #ifdef DEBUG_FRAME_DIFF
   459             QImage imagePrevious(16,16,QImage::Format_RGB32);
   460             QImage imageNext(16,16,QImage::Format_RGB32);
   461 #endif
   462 
   463             //Get both our blocks from our buffers
   464             for (int x=i;x<i+16;x++)
   465                 {
   466                 for (int y=j;y<j+16;y++)
   467                     {
   468                     nextBlock.SetBitValue((x-i)*16+(y-j),(*iFrameNext)[x*h+y]);
   469                     previousBlock.SetBitValue((x-i)*16+(y-j),(*iFramePrevious)[x*h+y]);
   470 
   471 #ifdef DEBUG_FRAME_DIFF
   472                     imageNext.setPixel(x-i,y-j,(nextBlock[(x-i)*16+(y-j)]?0xFFFFFFFF:0x00000000));
   473                     imagePrevious.setPixel(x-i,y-j,(previousBlock[(x-i)*16+(y-j)]?0xFFFFFFFF:0x00000000));
   474 #endif
   475                     }
   476                 }
   477 
   478 #ifdef DEBUG_FRAME_DIFF
   479             QString previousName;
   480             QString nextName;
   481             QTextStream(&previousName) << "p" << i << "x" << j << ".png";
   482             QTextStream(&nextName) << "n" << i << "x" << j << ".png";
   483             imagePrevious.save(previousName);
   484             imageNext.save(nextName);
   485 #endif
   486 
   487 
   488             //if (memcmp(iFrameNext->Ptr()+offset,iFramePrevious->Ptr()+offset,32 )) //32=(16*16/8)
   489             if (memcmp(nextBlock.Ptr(),previousBlock.Ptr(),32 )!=0) //32=(16*16/8)
   490                 {
   491                 //We need to update that block
   492                 SetPixelBlock(i,j,15,32,nextBlock.Ptr());
   493                 //SetPixelBlock(i,j,15,32,0xFF/*nextBlock.Ptr()*/);
   494                 //SetDisplayPosition(iDisplayPositionX,OffScreenY());
   495                 //SetDisplayPosition(iDisplayPositionX,OffScreenY());
   496 
   497                 //SetPixelBlock(i,j,15,32,iFrameNext->Ptr()+offset);
   498                 }
   499             }
   500         }
   501 
   502     }
   503 
   504 /**
   505 Translate the given pixel coordinate according to our off screen mode.
   506 */
   507 void GP1212A01A::OffScreenTranslation(unsigned char& aX, unsigned char& aY)
   508 	{
   509 	if (OffScreenMode())
   510 		{
   511 		aX+=WidthInPixels()-iDisplayPositionX;
   512 		aY+=HeightInPixels()-iDisplayPositionY;
   513 		}
   514 	}
   515 
   516 
   517 /**
   518 */
   519 void GP1212A01A::ResetBuffers()
   520 	{
   521     //iNextFrame->ClearAll();
   522     //memset(iFrameAlpha,0x00,sizeof(iFrameAlpha));
   523 	//memset(iFrameBeta,0x00,sizeof(iFrameBeta));
   524 	}
   525 
   526 /**
   527 */
   528 void GP1212A01A::RequestDeviceId()
   529     {
   530     if (RequestPending())
   531         {
   532         //Abort silently for now
   533         return;
   534         }
   535 
   536     //1BH,5BH,63H,49H,44H
   537     //Send Read ID command
   538     FutabaVfdReport report;
   539     report[0]=0x00; //Report ID
   540     report[1]=0x05; //Report length
   541     report[2]=0x1B; //Command ID
   542     report[3]=0x5B; //Command ID
   543     report[4]=0x63; //Command ID
   544     report[5]=0x49; //Command ID
   545     report[6]=0x44; //Command ID
   546     if (Write(report)==report.Size())
   547         {
   548         iRequest=ERequestDeviceId;
   549         }
   550     }
   551 
   552 /**
   553 */
   554 void GP1212A01A::RequestFirmwareRevision()
   555     {
   556     if (RequestPending())
   557         {
   558         //Abort silently for now
   559         return;
   560         }
   561 
   562     //1BH,5BH,63H,46H,52H
   563     //Send Software Revision Read Command
   564     FutabaVfdReport report;
   565     report[0]=0x00; //Report ID
   566     report[1]=0x05; //Report length
   567     report[2]=0x1B; //Command ID
   568     report[3]=0x5B; //Command ID
   569     report[4]=0x63; //Command ID
   570     report[5]=0x46; //Command ID
   571     report[6]=0x52; //Command ID
   572     if (Write(report)==report.Size())
   573         {
   574         iRequest=ERequestFirmwareRevision;
   575         }
   576     }
   577 
   578 /**
   579 */
   580 void GP1212A01A::RequestPowerSupplyStatus()
   581     {
   582     if (RequestPending())
   583         {
   584         //Abort silently for now
   585         return;
   586         }
   587     //1BH,5BH,63H,50H,4DH
   588     //Send Power Suppply Monitor Command
   589     FutabaVfdReport report;
   590     report[0]=0x00; //Report ID
   591     report[1]=0x05; //Report length
   592     report[2]=0x1B; //Command ID
   593     report[3]=0x5B; //Command ID
   594     report[4]=0x63; //Command ID
   595     report[5]=0x50; //Command ID
   596     report[6]=0x4D; //Command ID
   597     if (Write(report)==report.Size())
   598         {
   599         iRequest=ERequestPowerSupplyStatus;
   600         }
   601     }
   602 
   603 
   604 /**
   605 This is for development purposes only.
   606 Production application should stick to off-screen mode to avoid tearing.
   607 */
   608 void GP1212A01A::ToggleOffScreenMode()
   609 	{
   610     SetOffScreenMode(!iOffScreenMode);
   611 	}
   612 
   613 /**
   614  * @brief GP1212A01A::SetOffScreenMode
   615  * @param aOn
   616  * @return
   617  */
   618 void GP1212A01A::SetOffScreenMode(bool aOn)
   619     {
   620     if (aOn==iOffScreenMode)
   621     {
   622         //Nothing to do here
   623         return;
   624     }
   625 
   626     iOffScreenMode=aOn;
   627 
   628     //Clean up our buffers upon switching modes
   629     SetDisplayPosition(0,0);
   630     Clear();
   631     SwapBuffers();
   632     Clear();
   633     }
   634 
   635 /**
   636  */
   637 GP1212A01A::Request GP1212A01A::AttemptRequestCompletion()
   638     {
   639     if (!RequestPending())
   640         {
   641         return ERequestNone;
   642         }
   643 
   644     int res=Read(iInputReport);
   645 
   646     if (!res)
   647         {
   648         return ERequestNone;
   649         }
   650 
   651     //Process our request
   652     if (CurrentRequest()==GP1212A01A::ERequestPowerSupplyStatus)
   653         {
   654         if (iInputReport[1]==0x4F && iInputReport[2]==0x4E)
   655             {
   656             iPowerOn = true;
   657             }
   658         else if (iInputReport[1]==0x4F && iInputReport[2]==0x46 && iInputReport[3]==0x46)
   659             {
   660             iPowerOn = false;
   661             }
   662         }
   663 
   664     Request completed=iRequest;
   665     //Our request was completed
   666     iRequest=ERequestNone;
   667 
   668     return completed;
   669     }
   670 
   671 
   672 /**
   673 Set our screen brightness.
   674 @param The desired brightness level. Must be between MinBrightness and MaxBrightness.
   675 */
   676 void GP1212A01A::SetBrightness(int aBrightness)
   677     {
   678     if (aBrightness<MinBrightness()||aBrightness>MaxBrightness())
   679         {
   680         //Brightness out of range.
   681         //Just ignore that request.
   682         return;
   683         }
   684 
   685     FutabaVfdReport report;
   686     report[0]=0x00; //Report ID
   687     report[1]=0x06; //Report size
   688     report[2]=0x1B; //Command ID
   689     report[3]=0x5C; //Command ID
   690     report[4]=0x3F; //Command ID
   691     report[5]=0x4C; //Command ID
   692     report[6]=0x44; //Command ID
   693     report[7]=0x30+aBrightness; //Brightness level
   694     Write(report);
   695     }
   696 
   697