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