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