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