FutabaMDM166AA.cpp
author StephaneLenclud
Sat, 07 Feb 2015 13:50:11 +0100
changeset 32 2c844ef1ff4b
parent 31 0a2b658e0d56
child 33 fc42477ae80b
permissions -rw-r--r--
MDM166AA: Improved icon APIs.
     1 //
     2 //
     3 //
     4 
     5 #include "FutabaMDM166AA.h"
     6 
     7 #include <stdio.h>
     8 #include <time.h>
     9 
    10 
    11 
    12 typedef void (MDM166AA::*TSetIconStatus) (int aIndex, int aStatus);
    13 
    14 const TSetIconStatus KFunctionPerIcon[]=
    15 	{
    16 	&MDM166AA::SetIconNetwork,	//EMiniDisplayIconNetwork,
    17     &MDM166AA::SetIconEmail,	//EMiniDisplayIconEmail,
    18     &MDM166AA::SetIconMute,	//EMiniDisplayIconMute,
    19     &MDM166AA::SetIconVolume, //EMiniDisplayIconVolume,
    20 	&MDM166AA::SetIconVolumeLabel,	//EMiniDisplayIconVolumeLabel,
    21 	&MDM166AA::SetIconPlay,	//EMiniDisplayIconPlay,
    22 	&MDM166AA::SetIconPause,	//EMiniDisplayIconPause,
    23 	&MDM166AA::SetIconRecording	//EMiniDisplayIconRecording
    24 	};
    25 
    26 const int KMaxIconType = sizeof(KFunctionPerIcon)/sizeof(TSetIconStatus);
    27 
    28 /**
    29 Define how segments each of our icons have.
    30 Order matters.
    31 */
    32 const int KSegmentsPerIcon[]=
    33 	{
    34 	4,	//EMiniDisplayIconNetwork,
    35     2,	//EMiniDisplayIconEmail,
    36     1,	//EMiniDisplayIconMute,
    37     14, //EMiniDisplayIconVolume,
    38 	1,	//EMiniDisplayIconVolumeLabel,
    39 	1,	//EMiniDisplayIconPlay,
    40 	1,	//EMiniDisplayIconPause,
    41 	1	//EMiniDisplayIconRecording
    42 	};
    43 
    44 /**
    45 Define how status each of our icon can assume.
    46 Its typically two for On and Off status.
    47 */
    48 const int KStatusPerIcon[]=
    49 	{
    50 	2,	//EMiniDisplayIconNetwork,
    51     2,	//EMiniDisplayIconEmail,
    52     2,	//EMiniDisplayIconMute,
    53     3, //EMiniDisplayIconVolume,
    54 	2,	//EMiniDisplayIconVolumeLabel,	2,	//EMiniDisplayIconPlay,
    55 	2,	//EMiniDisplayIconPause,
    56 	2	//EMiniDisplayIconRecording
    57 	};
    58 
    59 
    60 
    61 static void sleep(unsigned int mseconds)
    62 	{
    63     clock_t goal = mseconds + clock();
    64     while (goal > clock());
    65 	}
    66 
    67 //
    68 // class MDM166AA
    69 //
    70 
    71 MDM166AA::MDM166AA():
    72     iOffScreenMode(true),
    73 	iNeedAccurateClockData(false),
    74     iFrameNext(NULL),
    75     iFrameCurrent(NULL),
    76     iFramePrevious(NULL),
    77     iFrameAlpha(NULL),
    78     iFrameBeta(NULL),
    79     iFrameGamma(NULL)
    80 	{
    81 	iDeviceId[0]=0;
    82 	iFirmwareRevision[0]=0;
    83 	//ResetBuffers();
    84 	}
    85 
    86 /**
    87 */
    88 MDM166AA::~MDM166AA()
    89 	{
    90     delete iFrameAlpha;
    91     iFrameAlpha=NULL;
    92     //
    93     delete iFrameBeta;
    94     iFrameBeta=NULL;
    95     //
    96     delete iFrameGamma;
    97     iFrameGamma=NULL;
    98     //
    99     iFrameNext=NULL;
   100     iFrameCurrent=NULL;
   101     iFramePrevious=NULL;
   102 	}
   103 
   104 /**
   105 */
   106 int MDM166AA::Open()
   107 	{
   108 	int success = HidDevice::Open(KTargaVendorId,KFutabaProductIdMDM166AA,NULL);
   109 	if (success)
   110 		{
   111         //Allocate both frames
   112         delete iFrameAlpha;
   113         iFrameAlpha=NULL;
   114         iFrameAlpha=new BitArrayLow(KMDM166AAFrameBufferPixelCount);
   115         //
   116         delete iFrameBeta;
   117         iFrameBeta=NULL;
   118         iFrameBeta=new BitArrayLow(KMDM166AAFrameBufferPixelCount);
   119         //
   120         delete iFrameGamma;
   121         iFrameGamma=NULL;
   122         iFrameGamma=new BitArrayLow(KMDM166AAFrameBufferPixelCount);
   123         //
   124         iFrameNext=iFrameAlpha;
   125         iFrameCurrent=iFrameBeta;
   126         iFramePrevious=iFrameGamma;
   127         //
   128 		SetNonBlocking(1);
   129 		//
   130 		SendCommandReset();
   131 		
   132 		//We will need accurate clock data
   133 		iNeedAccurateClockData=true;
   134 		//Until we get it just use rough time instead
   135 		//We don't set clock data here as it turns on clock display too and cause an unpleasant clock flash
   136 		//Only side effect from not doing this here is that for at most one minute the first time you cold boot your display the time should be wrong.
   137 		//SetClockData();
   138 
   139 		//Turns mast ON
   140 		//SetIconNetwork(0,EIconOn);
   141 		//Show volume label
   142 		//SendCommandSymbolControl(EIconVolumeLabel,EIconOn);
   143 		//Icon checks
   144 		//SetAllIcons(EIconOn);
   145 		}
   146 	return success;
   147 	}
   148 
   149 /**
   150 */
   151 void MDM166AA::SetPixel(unsigned char aX, unsigned char aY, unsigned int aPixel)
   152 	{
   153 	//
   154 	//int byteOffset=(aX*HeightInPixels()+aY)/8;
   155 	//int bitOffset=(aX*HeightInPixels()+aY)%8;
   156     //iNextFrame[byteOffset] |= ( (aOn?0x01:0x00) << bitOffset );
   157 
   158 	//Pixel is on if any of the non-alpha component is not null
   159 	bool on = (aPixel&0x00FFFFFF)!=0x00000000;
   160 
   161     if (iOffScreenMode)
   162         {
   163         if (on)
   164             {
   165             iFrameNext->SetBit(aX*HeightInPixels()+aY);
   166             }
   167         else
   168             {
   169             iFrameNext->ClearBit(aX*HeightInPixels()+aY);
   170             }
   171         }
   172     else
   173         {
   174         //Just specify a one pixel block
   175         //TODO
   176         }
   177 	}
   178 
   179 /**
   180 Clear our client side back buffer.
   181 Call to SwapBuffers must follow to actually clear the display.
   182 */
   183 void MDM166AA::Clear()
   184     {
   185 	//That one also clear the symbols
   186     SetAllPixels(0x00);
   187 	SendCommandClear(); //Clear icons too
   188     }
   189 
   190 /**
   191 Turn on all pixels.
   192 Must be followed by a SwapBuffers call.
   193 */
   194 void MDM166AA::Fill()
   195 	{
   196 	SetAllPixels(0xFF);
   197 	}
   198 
   199 /**
   200 Set all pixels on our screen to the desired value.
   201 This operation is performed off screen to avoid tearing.
   202 @param 8 pixels pattern
   203 */
   204 void MDM166AA::SetAllPixels(unsigned char aPattern)
   205 	{
   206 	//With a single buffer
   207 	//unsigned char screen[2048]; //One screen worth of pixels
   208 	//memset(screen,0xFF,sizeof(screen));
   209 	//SetPixelBlock(0,0,63,sizeof(screen),screen);
   210 
   211 
   212     if (iOffScreenMode)
   213         {
   214         memset(iFrameNext->Ptr(),aPattern,FrameBufferSizeInBytes());
   215         }
   216     else
   217         {
   218         //Using pattern SetPixelBlock variant.
   219         //TODO
   220         }
   221 	//
   222 	}
   223 
   224 
   225 
   226 
   227 
   228 
   229 /**
   230 Whole display RAM areas including invisible area are filled with 00H data.
   231 (Include the symbol)
   232 SL: Though there is no invisible area with that device.
   233 */
   234 void MDM166AA::SendCommandClear()
   235 	{
   236     //Send Clear Display Command
   237 	FutabaVfdReport report;
   238 	report[0]=0x00; //Report ID
   239 	report[1]=0x02; //Report length
   240 	report[2]=0x1B; //Command ID
   241 	report[3]=0x50; //Command ID
   242 	Write(report);
   243 	}
   244 
   245 /**
   246 Check if accurate clock data is needed and update display clock if system clock seconds are zero.
   247 This is intended to be called every frame from our SwapBuffers function.
   248 */
   249 void MDM166AA::AttemptClockSynchronization()
   250 	{
   251 	//Check if accurate clock data is needed
   252 	if (!iNeedAccurateClockData)
   253 		{
   254 		return;
   255 		}
   256 
   257 	//Fetch local time
   258 	time_t rawtime;
   259 	struct tm * timeinfo;
   260 	time ( &rawtime );
   261 	timeinfo = localtime ( &rawtime );
   262 
   263 	//If our seconds are zero we synchronize our display clock
   264 	if (timeinfo->tm_sec==0)
   265 		{
   266 		SendCommandSetClockData(timeinfo->tm_hour,timeinfo->tm_min);
   267 		//Our clock is as accurate as it can for the time being
   268 		iNeedAccurateClockData=false;
   269 		}
   270 	}
   271 
   272 /**
   273 Put our off screen buffer on screen.
   274 On screen buffer goes off screen.
   275 */
   276 void MDM166AA::SwapBuffers()
   277 	{
   278 	//We need to synchronize our clock seconds
   279 	AttemptClockSynchronization();
   280 
   281 	//Only perform buffer swapping if off screen mode is enabled
   282 	if (OffScreenMode())
   283 		{
   284 		//Send next frame to our display RAM
   285 		//We could attempt to implement a frame differencing algorithm much like we did for GP1212A01.
   286 		//However we see little point doing that since we already run at above 20 FPS.
   287 		SendCommandWriteGraphicData(FrameBufferSizeInBytes(),iFrameNext->Ptr());
   288 
   289         //Cycle through our frame buffers
   290         //We keep track of previous frame which is in fact our device back buffer.
   291         //We can then compare previous and next frame and send only the differences to our device.
   292         //This mechanism allows us to reduce traffic over our USB bus thus improving our frame rate from 14 FPS to 30 FPS.
   293         //Keep our previous frame pointer
   294         BitArrayLow* previousFrame=iFramePrevious;
   295         //Current frame becomes the previous one
   296         iFramePrevious = iFrameCurrent;
   297         //Next frame becomes the current one
   298         iFrameCurrent = iFrameNext;
   299         //Next frame is now our former previous
   300         iFrameNext = previousFrame;
   301 		}
   302 	}
   303 
   304 
   305 //Define the edge of our pixel block
   306 //Pixel blocks of 32x32 seems to run almost as fast as full screen update in worse case scenarii.
   307 //Though I wonder if in some situations 16 could be better. Make this an attribute at some point if need be.
   308 const int KPixelBlockEdge = 32;
   309 const int KPixelBlockSizeInBits = KPixelBlockEdge*KPixelBlockEdge;
   310 const int KPixelBlockSizeInBytes = KPixelBlockSizeInBits/8;
   311 
   312 
   313 /**
   314 */
   315 void MDM166AA::Request(TMiniDisplayRequest aRequest)
   316 	{
   317 	switch (aRequest)
   318 		{
   319 	case EMiniDisplayRequestDeviceId:
   320 		RequestDeviceId();
   321 		break;
   322 	case EMiniDisplayRequestFirmwareRevision:
   323 		RequestFirmwareRevision();
   324 		break;
   325 	case EMiniDisplayRequestPowerSupplyStatus:
   326 		RequestPowerSupplyStatus();
   327 		break;
   328 	default:
   329 		//Not supported
   330 		break;
   331 		};
   332 	}
   333 
   334 
   335 /**
   336 */
   337 void MDM166AA::ResetBuffers()
   338 	{
   339     //iNextFrame->ClearAll();
   340     //memset(iFrameAlpha,0x00,sizeof(iFrameAlpha));
   341 	//memset(iFrameBeta,0x00,sizeof(iFrameBeta));
   342 	}
   343 
   344 /**
   345 */
   346 void MDM166AA::RequestDeviceId()
   347     {
   348 	//Not supported
   349     }
   350 
   351 /**
   352 */
   353 void MDM166AA::RequestFirmwareRevision()
   354     {
   355 	//Not supported
   356     }
   357 
   358 /**
   359 */
   360 void MDM166AA::RequestPowerSupplyStatus()
   361     {
   362 	//Not supported
   363     }
   364 
   365 
   366 /**
   367 This is for development purposes only.
   368 Production application should stick to off-screen mode to avoid tearing.
   369 */
   370 void MDM166AA::ToggleOffScreenMode()
   371 	{
   372     SetOffScreenMode(!iOffScreenMode);
   373 	}
   374 
   375 /**
   376  * @brief MDM166AA::SetOffScreenMode
   377  * @param aOn
   378  * @return
   379  */
   380 void MDM166AA::SetOffScreenMode(bool aOn)
   381     {
   382     if (aOn==iOffScreenMode)
   383     {
   384         //Nothing to do here
   385         return;
   386     }
   387 
   388     iOffScreenMode=aOn;
   389 
   390     //Clean up our buffers upon switching modes
   391     Clear();
   392     SwapBuffers();
   393     Clear();
   394     }
   395 
   396 /**
   397 Set our screen brightness.
   398 @param The desired brightness level. Must be between MinBrightness and MaxBrightness.
   399 */
   400 void MDM166AA::SetBrightness(int aBrightness)
   401     {
   402     if (aBrightness<MinBrightness()||aBrightness>MaxBrightness())
   403         {
   404         //Brightness out of range.
   405         //Just ignore that request.
   406         return;
   407         }
   408 
   409     FutabaVfdReport report;
   410     report[0]=0x00; //Report ID
   411     report[1]=0x03; //Report size
   412     report[2]=0x1B; //Command ID
   413     report[3]=0x40; //Command ID
   414     report[4]=aBrightness; //Brightness level
   415     Write(report);
   416     }
   417 
   418 
   419 /**
   420 */
   421 void MDM166AA::ShowClock()
   422 	{
   423 	//Assuming display clock is at least roughly set since we do it when opening our display connection.
   424 	//We will need accurate clock data next we get a chance.
   425 	//This should guarantee that if our display remain open for weeks our clock will be synchronized whenever we switch back from clock mode to render mode.
   426 	iNeedAccurateClockData=true;
   427 	//Show clock using specified styles
   428 	SendCommandClockDisplay(EClockLarge,EClock24);
   429 	}
   430 
   431 /**
   432 */
   433 void MDM166AA::HideClock()
   434 	{
   435 	//TODO: or reset
   436 	Clear();
   437 	}
   438 
   439 /**
   440 */
   441 int MDM166AA::IconCount(TMiniDisplayIconType aIcon)
   442 	{
   443 	return KSegmentsPerIcon[aIcon];
   444 	}
   445 
   446 int MDM166AA::IconStatusCount(TMiniDisplayIconType aIcon)
   447 	{
   448 	return KStatusPerIcon[aIcon];
   449 	}
   450 
   451 void MDM166AA::SetIconStatus(TMiniDisplayIconType aIcon, int aIndex, int aStatus)
   452 	{
   453 	if (aIcon<0||aIcon>=KMaxIconType||(KFunctionPerIcon[aIcon]==NULL))
   454 		{
   455 		//Out of range or no function pointer for that icon
   456 		return;
   457 		}
   458 
   459 	(this->*KFunctionPerIcon[aIcon])(aIndex,aStatus);
   460 	}
   461 
   462 /**
   463 */
   464 void MDM166AA::SetIconNetwork(int aIndex, int aStatus)
   465 	{
   466 	if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconNetwork])
   467 		{
   468 		//Out of range
   469 		return;
   470 		}
   471 
   472 	SendCommandSymbolControl((TIconId)(aIndex+EIconNetworkMast),(aStatus==0?EIconOff:EIconOn));
   473 	}
   474 
   475 /**
   476 */
   477 void MDM166AA::SetIconEmail(int aIndex, int aStatus)
   478 	{
   479 	if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconEmail])
   480 		{
   481 		//Out of range
   482 		return;
   483 		}
   484 
   485 	SendCommandSymbolControl((TIconId)(aIndex+EIconEnvelop),(aStatus==0?EIconOff:EIconOn));
   486 	}
   487 
   488 /**
   489 */
   490 void MDM166AA::SetIconMute(int aIndex, int aStatus)
   491 	{
   492 	if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconMute])
   493 		{
   494 		//Out of range
   495 		return;
   496 		}
   497 
   498 	SendCommandSymbolControl((TIconId)(aIndex+EIconMute),(aStatus==0?EIconOff:EIconOn));
   499 	}
   500 
   501 /**
   502 */
   503 void MDM166AA::SetIconVolume(int aIndex, int aStatus)
   504 	{
   505 	if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconVolume])
   506 		{
   507 		//Out of range
   508 		return;
   509 		}
   510 
   511 	if (aStatus<EIconOff)
   512 		{
   513 		//Assuming we just want to turn it off then
   514 		aStatus=EIconOff;
   515 		}
   516 
   517 	//Make sure we cap at our highest status value
   518 	aStatus = MIN(EIconOn,aStatus);
   519 
   520 	SendCommandSymbolControl((TIconId)(aIndex+EIconVolumeLevel01),(TIconStatus)aStatus);
   521 	}
   522 
   523 
   524 /**
   525 */
   526 void MDM166AA::SetIconVolumeLabel(int aIndex, int aStatus)
   527 	{
   528 	if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconMute])
   529 		{
   530 		//Out of range
   531 		return;
   532 		}
   533 
   534 	SendCommandSymbolControl((TIconId)(aIndex+EIconVolumeLabel),(aStatus==0?EIconOff:EIconOn));
   535 	}
   536 
   537 
   538 /**
   539 */
   540 void MDM166AA::SetIconPlay(int aIndex, int aStatus)
   541 	{
   542 	if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconPlay])
   543 		{
   544 		//Out of range
   545 		return;
   546 		}
   547 
   548 	SendCommandSymbolControl((TIconId)(aIndex+EIconPlay),(aStatus==0?EIconOff:EIconOn));
   549 	}
   550 
   551 
   552 /**
   553 */
   554 void MDM166AA::SetIconPause(int aIndex, int aStatus)
   555 	{
   556 	if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconPause])
   557 		{
   558 		//Out of range
   559 		return;
   560 		}
   561 
   562 	SendCommandSymbolControl((TIconId)(aIndex+EIconPause),(aStatus==0?EIconOff:EIconOn));
   563 	}
   564 
   565 
   566 /**
   567 */
   568 void MDM166AA::SetIconRecording(int aIndex, int aStatus)
   569 	{
   570 	if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconRecording])
   571 		{
   572 		//Out of range
   573 		return;
   574 		}
   575 
   576 	SendCommandSymbolControl((TIconId)(aIndex+EIconRecording),(aStatus==0?EIconOff:EIconOn));
   577 	}
   578 
   579 /**
   580 Set all our icons to the corresponding status.
   581 */
   582 void MDM166AA::SetAllIcons(TIconStatus aStatus)
   583 	{
   584 	for (int i=EIconFirst;i<=EIconLast;i++)
   585 		{
   586 		SendCommandSymbolControl((TIconId)i,aStatus);
   587 		}
   588 	}
   589 
   590 /**
   591 Symbols control
   592 Segment On/Off and Grayscale/Brightness
   593 [Code]1BH,30H,Ps,Pb
   594 */
   595 void MDM166AA::SendCommandSymbolControl(TIconId aIconId, TIconStatus aStatus)
   596 	{
   597 	FutabaVfdReport report;
   598     report[0]=0x00; //Report ID
   599     report[1]=0x04; //Report size
   600     report[2]=0x1B; //Command ID
   601     report[3]=0x30; //Command ID
   602 	report[4]=aIconId;
   603 	report[5]=aStatus;
   604 
   605     Write(report);
   606 	}
   607 
   608 
   609 /**
   610 Clock setting 
   611 [Code]1BH,00H,Pm,Ph 
   612 [Function]Setting the clock data. The setting data is cleared, if the Reset command is input or power is turned off.
   613 Ph = hour 
   614 Pm = minute 
   615 */
   616 void MDM166AA::SendCommandSetClockData(unsigned char aHour, unsigned char aMinute)
   617 	{
   618 	FutabaVfdReport report;
   619     report[0]=0x00; //Report ID
   620     report[1]=0x04; //Report size
   621     report[2]=0x1B; //Command ID
   622     report[3]=0x00; //Command ID
   623 
   624 	//Minutes and Hours needs to be in hexadecimal view
   625 	//To get 21:59 you need to pass in 0x21:0x59
   626 	//Weirdest format ever, I know 
   627 	report[4]=(aMinute/10*16)+aMinute%10;
   628 	report[5]=(aHour/10*16)+aHour%10;
   629 
   630     Write(report);
   631 	}
   632 
   633 /**
   634 Set display clock data according to local system time.
   635 This will only provide 30s accuracy.
   636 In fact display clock seconds are set to zero whenever clock data is set.
   637 So you would only get second accuracy if this function was called when system time is at zero second.
   638 It's the responsibility of AttemptClockSynchronization function to obtain second accuracy.
   639 The present function is intended to provide only rough clock synchronization.
   640 
   641 @note Unfortunately this command also turns on clock display.
   642 */
   643 void MDM166AA::SetClockData()
   644 	{
   645 	time_t rawtime;
   646 	struct tm * timeinfo;
   647 
   648 	time ( &rawtime );
   649 	timeinfo = localtime ( &rawtime );
   650 	//Adjust minute as best as we can so that we have a 30 seconds offset at most rather a than a full minute.
   651 	if (timeinfo->tm_sec>30)
   652 		{
   653 		//Use the next minute then
   654 		timeinfo->tm_min++;
   655 		if (timeinfo->tm_min==60)
   656 			{
   657 			//Use the next hour then
   658 			timeinfo->tm_hour++;
   659 			timeinfo->tm_min=0;
   660 			if (timeinfo->tm_hour==24)
   661 				{
   662 				//Move to the next day then
   663 				timeinfo->tm_hour=0;
   664 				}
   665 			}
   666 		}
   667 
   668 	//Send hours and minutes to our display
   669 	SendCommandSetClockData(timeinfo->tm_hour,timeinfo->tm_min);
   670 	}
   671 
   672 
   673 /**
   674 Clock display
   675 [Code] 1BH,Ps,aL,aH,Pf
   676 [Function] Clock is displayed small or big.
   677 */
   678 void MDM166AA::SendCommandClockDisplay(TClockSize aClockSize, TClockFormat aClockFormat)
   679 	{
   680 	FutabaVfdReport report;
   681     report[0]=0x00; //Report ID
   682     report[1]=0x03; //Report size
   683     report[2]=0x1B; //Command ID
   684     report[3]=aClockSize; //
   685     report[4]=aClockFormat; //
   686 
   687     Write(report);
   688 	}
   689 
   690 
   691 /**
   692 Display RAM filled with 00H.
   693 Address Counter is set by 00H.
   694 Dimming is set to 50%.
   695 Turn off all icons segments.
   696 */
   697 void MDM166AA::SendCommandReset()
   698 	{
   699 	FutabaVfdReport report;
   700 	report[0]=0x00; //Report ID
   701 	report[1]=0x01; //Report length.
   702 	report[2]=0x1F; //Command ID
   703 	Write(report);
   704 	}
   705 
   706 
   707 /**
   708 Set Address Counter (AC) values: 1BH + 60H + xxH
   709 xxH: 00 ~ BFH
   710 AC value represents the start address for graphic data.
   711 There are 192 bytes as display RAM. It can be set on anywhere even if AC value is not visible area.
   712 The default value is 00H.
   713 Default: 00H
   714 When clock is displayed, AC value is set 00H.
   715 */
   716 void MDM166AA::SendCommandSetAddressCounter(unsigned char aAddressCounter)
   717 	{
   718 	FutabaVfdReport report;
   719 	report[0]=0x00; //Report ID
   720 	report[1]=0x03; //Report length.
   721 	report[2]=0x1B; //Command ID
   722 	report[3]=0x60; //Command ID
   723 	report[4]=aAddressCounter;
   724 	Write(report);
   725 	}
   726 
   727 
   728 /**
   729 Set the defined pixel block to the given value.
   730 
   731 @param The size of our pixel data. Number of pixels divided by 8.
   732 @param Pointer to our pixel data.
   733 */
   734 void MDM166AA::SendCommandWriteGraphicData(int aSize, unsigned char* aPixels)
   735     {
   736 	//TODO: Remove that at some point
   737 	SendCommandSetAddressCounter(0);
   738 
   739 	const int KMaxPixelBytes=48;
   740 	const int KHeaderSize=3;
   741 	
   742 	int remainingSize=aSize;
   743 	int sizeWritten=0;
   744 
   745 	while (remainingSize>0)
   746 		{
   747 		//Only send a maximum of 48 bytes worth of pixels per report
   748 		const int KPixelDataSize=(remainingSize<=KMaxPixelBytes?remainingSize:KMaxPixelBytes);
   749 
   750 		FutabaVfdReport report;
   751 		report[0]=0x00; //Report ID
   752 		report[1]=KPixelDataSize+KHeaderSize; //Report length. +3 is for our header first 3 bytes.
   753 		report[2]=0x1B; //Command ID
   754 		report[3]=0x70; //Command ID
   755 		report[4]=KPixelDataSize; //Size of pixel data in bytes		
   756 		memcpy(report.Buffer()+5, aPixels+sizeWritten, KPixelDataSize);
   757 		Write(report);
   758 		//Advance
   759 		sizeWritten+=KPixelDataSize;
   760 		remainingSize-=KPixelDataSize;
   761 		}
   762     }