StephaneLenclud@25: // StephaneLenclud@35: // Copyright (C) 2014-2015 Stéphane Lenclud. StephaneLenclud@25: // StephaneLenclud@35: // This file is part of MiniDisplay. StephaneLenclud@35: // StephaneLenclud@35: // MiniDisplay is free software: you can redistribute it and/or modify StephaneLenclud@35: // it under the terms of the GNU General Public License as published by StephaneLenclud@35: // the Free Software Foundation, either version 3 of the License, or StephaneLenclud@35: // (at your option) any later version. StephaneLenclud@35: // StephaneLenclud@35: // MiniDisplay is distributed in the hope that it will be useful, StephaneLenclud@35: // but WITHOUT ANY WARRANTY; without even the implied warranty of StephaneLenclud@35: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the StephaneLenclud@35: // GNU General Public License for more details. StephaneLenclud@35: // StephaneLenclud@35: // You should have received a copy of the GNU General Public License StephaneLenclud@35: // along with MiniDisplay. If not, see . StephaneLenclud@25: // StephaneLenclud@25: StephaneLenclud@25: #include "FutabaMDM166AA.h" StephaneLenclud@25: StephaneLenclud@25: #include StephaneLenclud@25: #include StephaneLenclud@25: StephaneLenclud@25: StephaneLenclud@25: StephaneLenclud@32: typedef void (MDM166AA::*TSetIconStatus) (int aIndex, int aStatus); StephaneLenclud@32: StephaneLenclud@32: const TSetIconStatus KFunctionPerIcon[]= StephaneLenclud@32: { StephaneLenclud@33: &MDM166AA::SetIconNetworkSignal, //EMiniDisplayIconNetworkSignal, StephaneLenclud@33: &MDM166AA::SetIconInternet, //EMiniDisplayInternet, StephaneLenclud@33: &MDM166AA::SetIconEmail, //EMiniDisplayIconEmail, StephaneLenclud@32: &MDM166AA::SetIconMute, //EMiniDisplayIconMute, StephaneLenclud@32: &MDM166AA::SetIconVolume, //EMiniDisplayIconVolume, StephaneLenclud@32: &MDM166AA::SetIconVolumeLabel, //EMiniDisplayIconVolumeLabel, StephaneLenclud@32: &MDM166AA::SetIconPlay, //EMiniDisplayIconPlay, StephaneLenclud@32: &MDM166AA::SetIconPause, //EMiniDisplayIconPause, StephaneLenclud@32: &MDM166AA::SetIconRecording //EMiniDisplayIconRecording StephaneLenclud@32: }; StephaneLenclud@32: StephaneLenclud@32: const int KMaxIconType = sizeof(KFunctionPerIcon)/sizeof(TSetIconStatus); StephaneLenclud@32: StephaneLenclud@32: /** StephaneLenclud@32: Define how segments each of our icons have. StephaneLenclud@32: Order matters. StephaneLenclud@32: */ StephaneLenclud@32: const int KSegmentsPerIcon[]= StephaneLenclud@32: { StephaneLenclud@33: 3, //EMiniDisplayIconNetworkSignal, StephaneLenclud@33: 1, //EMiniDisplayIconInternet, StephaneLenclud@32: 2, //EMiniDisplayIconEmail, StephaneLenclud@32: 1, //EMiniDisplayIconMute, StephaneLenclud@32: 14, //EMiniDisplayIconVolume, StephaneLenclud@32: 1, //EMiniDisplayIconVolumeLabel, StephaneLenclud@32: 1, //EMiniDisplayIconPlay, StephaneLenclud@32: 1, //EMiniDisplayIconPause, StephaneLenclud@32: 1 //EMiniDisplayIconRecording StephaneLenclud@32: }; StephaneLenclud@32: StephaneLenclud@32: /** StephaneLenclud@32: Define how status each of our icon can assume. StephaneLenclud@32: Its typically two for On and Off status. StephaneLenclud@32: */ StephaneLenclud@32: const int KStatusPerIcon[]= StephaneLenclud@32: { StephaneLenclud@33: 2, //EMiniDisplayIconNetworkSignal, StephaneLenclud@33: 2, //EMiniDisplayIconInternet, StephaneLenclud@32: 2, //EMiniDisplayIconEmail, StephaneLenclud@32: 2, //EMiniDisplayIconMute, StephaneLenclud@33: 3, //EMiniDisplayIconVolume, StephaneLenclud@33: 2, //EMiniDisplayIconVolumeLabel, StephaneLenclud@33: 2, //EMiniDisplayIconPlay, StephaneLenclud@32: 2, //EMiniDisplayIconPause, StephaneLenclud@32: 2 //EMiniDisplayIconRecording StephaneLenclud@32: }; StephaneLenclud@32: StephaneLenclud@32: StephaneLenclud@32: StephaneLenclud@25: static void sleep(unsigned int mseconds) StephaneLenclud@25: { StephaneLenclud@25: clock_t goal = mseconds + clock(); StephaneLenclud@25: while (goal > clock()); StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: // StephaneLenclud@25: // class MDM166AA StephaneLenclud@25: // StephaneLenclud@25: StephaneLenclud@25: MDM166AA::MDM166AA(): StephaneLenclud@25: iOffScreenMode(true), StephaneLenclud@30: iNeedAccurateClockData(false), StephaneLenclud@25: iFrameNext(NULL), StephaneLenclud@25: iFrameCurrent(NULL), StephaneLenclud@25: iFramePrevious(NULL), StephaneLenclud@25: iFrameAlpha(NULL), StephaneLenclud@25: iFrameBeta(NULL), StephaneLenclud@28: iFrameGamma(NULL) StephaneLenclud@25: { StephaneLenclud@25: iDeviceId[0]=0; StephaneLenclud@25: iFirmwareRevision[0]=0; StephaneLenclud@25: //ResetBuffers(); StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@25: */ StephaneLenclud@25: MDM166AA::~MDM166AA() StephaneLenclud@25: { StephaneLenclud@25: delete iFrameAlpha; StephaneLenclud@25: iFrameAlpha=NULL; StephaneLenclud@25: // StephaneLenclud@25: delete iFrameBeta; StephaneLenclud@25: iFrameBeta=NULL; StephaneLenclud@25: // StephaneLenclud@25: delete iFrameGamma; StephaneLenclud@25: iFrameGamma=NULL; StephaneLenclud@25: // StephaneLenclud@25: iFrameNext=NULL; StephaneLenclud@25: iFrameCurrent=NULL; StephaneLenclud@25: iFramePrevious=NULL; StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@25: */ StephaneLenclud@25: int MDM166AA::Open() StephaneLenclud@25: { StephaneLenclud@25: int success = HidDevice::Open(KTargaVendorId,KFutabaProductIdMDM166AA,NULL); StephaneLenclud@25: if (success) StephaneLenclud@25: { StephaneLenclud@25: //Allocate both frames StephaneLenclud@25: delete iFrameAlpha; StephaneLenclud@25: iFrameAlpha=NULL; StephaneLenclud@25: iFrameAlpha=new BitArrayLow(KMDM166AAFrameBufferPixelCount); StephaneLenclud@25: // StephaneLenclud@25: delete iFrameBeta; StephaneLenclud@25: iFrameBeta=NULL; StephaneLenclud@25: iFrameBeta=new BitArrayLow(KMDM166AAFrameBufferPixelCount); StephaneLenclud@25: // StephaneLenclud@25: delete iFrameGamma; StephaneLenclud@25: iFrameGamma=NULL; StephaneLenclud@25: iFrameGamma=new BitArrayLow(KMDM166AAFrameBufferPixelCount); StephaneLenclud@25: // StephaneLenclud@25: iFrameNext=iFrameAlpha; StephaneLenclud@25: iFrameCurrent=iFrameBeta; StephaneLenclud@25: iFramePrevious=iFrameGamma; StephaneLenclud@25: // StephaneLenclud@25: SetNonBlocking(1); StephaneLenclud@25: // StephaneLenclud@28: SendCommandReset(); StephaneLenclud@30: StephaneLenclud@30: //We will need accurate clock data StephaneLenclud@34: //iNeedAccurateClockData=true; StephaneLenclud@30: //Until we get it just use rough time instead StephaneLenclud@34: //This is needed otherwise the clock won't work for the first minute. StephaneLenclud@34: //It flashes the clock when opening the display but that's no big deal. StephaneLenclud@34: ShowClock(); StephaneLenclud@34: SetClockData(); StephaneLenclud@31: StephaneLenclud@31: //Turns mast ON StephaneLenclud@31: //SetIconNetwork(0,EIconOn); StephaneLenclud@31: //Show volume label StephaneLenclud@31: //SendCommandSymbolControl(EIconVolumeLabel,EIconOn); StephaneLenclud@31: //Icon checks StephaneLenclud@31: //SetAllIcons(EIconOn); StephaneLenclud@25: } StephaneLenclud@25: return success; StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@25: */ StephaneLenclud@25: void MDM166AA::SetPixel(unsigned char aX, unsigned char aY, unsigned int aPixel) StephaneLenclud@25: { StephaneLenclud@25: // StephaneLenclud@25: //int byteOffset=(aX*HeightInPixels()+aY)/8; StephaneLenclud@25: //int bitOffset=(aX*HeightInPixels()+aY)%8; StephaneLenclud@25: //iNextFrame[byteOffset] |= ( (aOn?0x01:0x00) << bitOffset ); StephaneLenclud@25: StephaneLenclud@25: //Pixel is on if any of the non-alpha component is not null StephaneLenclud@25: bool on = (aPixel&0x00FFFFFF)!=0x00000000; StephaneLenclud@25: StephaneLenclud@25: if (iOffScreenMode) StephaneLenclud@25: { StephaneLenclud@25: if (on) StephaneLenclud@25: { StephaneLenclud@25: iFrameNext->SetBit(aX*HeightInPixels()+aY); StephaneLenclud@25: } StephaneLenclud@25: else StephaneLenclud@25: { StephaneLenclud@25: iFrameNext->ClearBit(aX*HeightInPixels()+aY); StephaneLenclud@25: } StephaneLenclud@25: } StephaneLenclud@25: else StephaneLenclud@25: { StephaneLenclud@25: //Just specify a one pixel block StephaneLenclud@25: //TODO StephaneLenclud@25: } StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@25: Clear our client side back buffer. StephaneLenclud@25: Call to SwapBuffers must follow to actually clear the display. StephaneLenclud@25: */ StephaneLenclud@25: void MDM166AA::Clear() StephaneLenclud@25: { StephaneLenclud@25: //That one also clear the symbols StephaneLenclud@28: SetAllPixels(0x00); StephaneLenclud@34: //SendCommandClear(); //Clear icons too StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@25: Turn on all pixels. StephaneLenclud@25: Must be followed by a SwapBuffers call. StephaneLenclud@25: */ StephaneLenclud@25: void MDM166AA::Fill() StephaneLenclud@25: { StephaneLenclud@25: SetAllPixels(0xFF); StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@25: Set all pixels on our screen to the desired value. StephaneLenclud@25: This operation is performed off screen to avoid tearing. StephaneLenclud@25: @param 8 pixels pattern StephaneLenclud@25: */ StephaneLenclud@25: void MDM166AA::SetAllPixels(unsigned char aPattern) StephaneLenclud@25: { StephaneLenclud@25: //With a single buffer StephaneLenclud@25: //unsigned char screen[2048]; //One screen worth of pixels StephaneLenclud@25: //memset(screen,0xFF,sizeof(screen)); StephaneLenclud@25: //SetPixelBlock(0,0,63,sizeof(screen),screen); StephaneLenclud@25: StephaneLenclud@25: StephaneLenclud@25: if (iOffScreenMode) StephaneLenclud@25: { StephaneLenclud@25: memset(iFrameNext->Ptr(),aPattern,FrameBufferSizeInBytes()); StephaneLenclud@25: } StephaneLenclud@25: else StephaneLenclud@25: { StephaneLenclud@25: //Using pattern SetPixelBlock variant. StephaneLenclud@25: //TODO StephaneLenclud@25: } StephaneLenclud@25: // StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: StephaneLenclud@25: StephaneLenclud@25: StephaneLenclud@25: StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@25: Whole display RAM areas including invisible area are filled with 00H data. StephaneLenclud@25: (Include the symbol) StephaneLenclud@25: SL: Though there is no invisible area with that device. StephaneLenclud@25: */ StephaneLenclud@25: void MDM166AA::SendCommandClear() StephaneLenclud@25: { StephaneLenclud@25: //Send Clear Display Command StephaneLenclud@25: FutabaVfdReport report; StephaneLenclud@25: report[0]=0x00; //Report ID StephaneLenclud@25: report[1]=0x02; //Report length StephaneLenclud@25: report[2]=0x1B; //Command ID StephaneLenclud@25: report[3]=0x50; //Command ID StephaneLenclud@25: Write(report); StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@30: Check if accurate clock data is needed and update display clock if system clock seconds are zero. StephaneLenclud@30: This is intended to be called every frame from our SwapBuffers function. StephaneLenclud@30: */ StephaneLenclud@30: void MDM166AA::AttemptClockSynchronization() StephaneLenclud@30: { StephaneLenclud@30: //Check if accurate clock data is needed StephaneLenclud@30: if (!iNeedAccurateClockData) StephaneLenclud@30: { StephaneLenclud@30: return; StephaneLenclud@30: } StephaneLenclud@30: StephaneLenclud@30: //Fetch local time StephaneLenclud@30: time_t rawtime; StephaneLenclud@30: struct tm * timeinfo; StephaneLenclud@30: time ( &rawtime ); StephaneLenclud@30: timeinfo = localtime ( &rawtime ); StephaneLenclud@30: StephaneLenclud@30: //If our seconds are zero we synchronize our display clock StephaneLenclud@30: if (timeinfo->tm_sec==0) StephaneLenclud@30: { StephaneLenclud@30: SendCommandSetClockData(timeinfo->tm_hour,timeinfo->tm_min); StephaneLenclud@30: //Our clock is as accurate as it can for the time being StephaneLenclud@30: iNeedAccurateClockData=false; StephaneLenclud@30: } StephaneLenclud@30: } StephaneLenclud@30: StephaneLenclud@30: /** StephaneLenclud@25: Put our off screen buffer on screen. StephaneLenclud@25: On screen buffer goes off screen. StephaneLenclud@25: */ StephaneLenclud@25: void MDM166AA::SwapBuffers() StephaneLenclud@25: { StephaneLenclud@30: //We need to synchronize our clock seconds StephaneLenclud@30: AttemptClockSynchronization(); StephaneLenclud@30: StephaneLenclud@25: //Only perform buffer swapping if off screen mode is enabled StephaneLenclud@25: if (OffScreenMode()) StephaneLenclud@25: { StephaneLenclud@28: //Send next frame to our display RAM StephaneLenclud@28: //We could attempt to implement a frame differencing algorithm much like we did for GP1212A01. StephaneLenclud@28: //However we see little point doing that since we already run at above 20 FPS. StephaneLenclud@27: SendCommandWriteGraphicData(FrameBufferSizeInBytes(),iFrameNext->Ptr()); StephaneLenclud@25: StephaneLenclud@25: //Cycle through our frame buffers StephaneLenclud@25: //We keep track of previous frame which is in fact our device back buffer. StephaneLenclud@25: //We can then compare previous and next frame and send only the differences to our device. StephaneLenclud@25: //This mechanism allows us to reduce traffic over our USB bus thus improving our frame rate from 14 FPS to 30 FPS. StephaneLenclud@25: //Keep our previous frame pointer StephaneLenclud@25: BitArrayLow* previousFrame=iFramePrevious; StephaneLenclud@25: //Current frame becomes the previous one StephaneLenclud@25: iFramePrevious = iFrameCurrent; StephaneLenclud@25: //Next frame becomes the current one StephaneLenclud@25: iFrameCurrent = iFrameNext; StephaneLenclud@25: //Next frame is now our former previous StephaneLenclud@25: iFrameNext = previousFrame; StephaneLenclud@25: } StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: StephaneLenclud@25: //Define the edge of our pixel block StephaneLenclud@25: //Pixel blocks of 32x32 seems to run almost as fast as full screen update in worse case scenarii. StephaneLenclud@25: //Though I wonder if in some situations 16 could be better. Make this an attribute at some point if need be. StephaneLenclud@25: const int KPixelBlockEdge = 32; StephaneLenclud@25: const int KPixelBlockSizeInBits = KPixelBlockEdge*KPixelBlockEdge; StephaneLenclud@25: const int KPixelBlockSizeInBytes = KPixelBlockSizeInBits/8; StephaneLenclud@25: StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@25: */ StephaneLenclud@25: void MDM166AA::Request(TMiniDisplayRequest aRequest) StephaneLenclud@25: { StephaneLenclud@25: switch (aRequest) StephaneLenclud@25: { StephaneLenclud@25: case EMiniDisplayRequestDeviceId: StephaneLenclud@25: RequestDeviceId(); StephaneLenclud@25: break; StephaneLenclud@25: case EMiniDisplayRequestFirmwareRevision: StephaneLenclud@25: RequestFirmwareRevision(); StephaneLenclud@25: break; StephaneLenclud@25: case EMiniDisplayRequestPowerSupplyStatus: StephaneLenclud@25: RequestPowerSupplyStatus(); StephaneLenclud@25: break; StephaneLenclud@25: default: StephaneLenclud@25: //Not supported StephaneLenclud@25: break; StephaneLenclud@25: }; StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@25: */ StephaneLenclud@25: void MDM166AA::ResetBuffers() StephaneLenclud@25: { StephaneLenclud@25: //iNextFrame->ClearAll(); StephaneLenclud@25: //memset(iFrameAlpha,0x00,sizeof(iFrameAlpha)); StephaneLenclud@25: //memset(iFrameBeta,0x00,sizeof(iFrameBeta)); StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@25: */ StephaneLenclud@25: void MDM166AA::RequestDeviceId() StephaneLenclud@25: { StephaneLenclud@25: //Not supported StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@25: */ StephaneLenclud@25: void MDM166AA::RequestFirmwareRevision() StephaneLenclud@25: { StephaneLenclud@28: //Not supported StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@25: */ StephaneLenclud@25: void MDM166AA::RequestPowerSupplyStatus() StephaneLenclud@25: { StephaneLenclud@25: //Not supported StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@25: This is for development purposes only. StephaneLenclud@25: Production application should stick to off-screen mode to avoid tearing. StephaneLenclud@25: */ StephaneLenclud@25: void MDM166AA::ToggleOffScreenMode() StephaneLenclud@25: { StephaneLenclud@25: SetOffScreenMode(!iOffScreenMode); StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@25: * @brief MDM166AA::SetOffScreenMode StephaneLenclud@25: * @param aOn StephaneLenclud@25: * @return StephaneLenclud@25: */ StephaneLenclud@25: void MDM166AA::SetOffScreenMode(bool aOn) StephaneLenclud@25: { StephaneLenclud@25: if (aOn==iOffScreenMode) StephaneLenclud@25: { StephaneLenclud@25: //Nothing to do here StephaneLenclud@25: return; StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: iOffScreenMode=aOn; StephaneLenclud@25: StephaneLenclud@25: //Clean up our buffers upon switching modes StephaneLenclud@25: Clear(); StephaneLenclud@25: SwapBuffers(); StephaneLenclud@25: Clear(); StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@25: Set our screen brightness. StephaneLenclud@25: @param The desired brightness level. Must be between MinBrightness and MaxBrightness. StephaneLenclud@25: */ StephaneLenclud@25: void MDM166AA::SetBrightness(int aBrightness) StephaneLenclud@25: { StephaneLenclud@25: if (aBrightnessMaxBrightness()) StephaneLenclud@25: { StephaneLenclud@25: //Brightness out of range. StephaneLenclud@25: //Just ignore that request. StephaneLenclud@25: return; StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: FutabaVfdReport report; StephaneLenclud@25: report[0]=0x00; //Report ID StephaneLenclud@25: report[1]=0x03; //Report size StephaneLenclud@25: report[2]=0x1B; //Command ID StephaneLenclud@25: report[3]=0x40; //Command ID StephaneLenclud@25: report[4]=aBrightness; //Brightness level StephaneLenclud@25: Write(report); StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@25: */ StephaneLenclud@25: void MDM166AA::ShowClock() StephaneLenclud@25: { StephaneLenclud@30: //Assuming display clock is at least roughly set since we do it when opening our display connection. StephaneLenclud@30: //We will need accurate clock data next we get a chance. StephaneLenclud@30: //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. StephaneLenclud@30: iNeedAccurateClockData=true; StephaneLenclud@30: //Show clock using specified styles StephaneLenclud@25: SendCommandClockDisplay(EClockLarge,EClock24); StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@25: */ StephaneLenclud@25: void MDM166AA::HideClock() StephaneLenclud@25: { StephaneLenclud@34: SetAllPixels(0x00); StephaneLenclud@34: SwapBuffers(); StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@32: /** StephaneLenclud@32: */ StephaneLenclud@32: int MDM166AA::IconCount(TMiniDisplayIconType aIcon) StephaneLenclud@32: { StephaneLenclud@32: return KSegmentsPerIcon[aIcon]; StephaneLenclud@32: } StephaneLenclud@32: StephaneLenclud@32: int MDM166AA::IconStatusCount(TMiniDisplayIconType aIcon) StephaneLenclud@32: { StephaneLenclud@32: return KStatusPerIcon[aIcon]; StephaneLenclud@32: } StephaneLenclud@32: StephaneLenclud@32: void MDM166AA::SetIconStatus(TMiniDisplayIconType aIcon, int aIndex, int aStatus) StephaneLenclud@32: { StephaneLenclud@32: if (aIcon<0||aIcon>=KMaxIconType||(KFunctionPerIcon[aIcon]==NULL)) StephaneLenclud@32: { StephaneLenclud@32: //Out of range or no function pointer for that icon StephaneLenclud@32: return; StephaneLenclud@32: } StephaneLenclud@32: StephaneLenclud@32: (this->*KFunctionPerIcon[aIcon])(aIndex,aStatus); StephaneLenclud@32: } StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@31: */ StephaneLenclud@33: void MDM166AA::SetIconNetworkSignal(int aIndex, int aStatus) StephaneLenclud@31: { StephaneLenclud@33: if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconNetworkSignal]) StephaneLenclud@33: { StephaneLenclud@33: //Out of range StephaneLenclud@33: return; StephaneLenclud@33: } StephaneLenclud@33: StephaneLenclud@33: SendCommandSymbolControl((TIconId)(aIndex+EIconNetworkSignalLow),(aStatus==0?EIconOff:EIconOn)); StephaneLenclud@33: } StephaneLenclud@33: StephaneLenclud@33: /** StephaneLenclud@33: */ StephaneLenclud@33: void MDM166AA::SetIconInternet(int aIndex, int aStatus) StephaneLenclud@33: { StephaneLenclud@33: if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconInternet]) StephaneLenclud@31: { StephaneLenclud@31: //Out of range StephaneLenclud@31: return; StephaneLenclud@31: } StephaneLenclud@31: StephaneLenclud@31: SendCommandSymbolControl((TIconId)(aIndex+EIconNetworkMast),(aStatus==0?EIconOff:EIconOn)); StephaneLenclud@31: } StephaneLenclud@31: StephaneLenclud@31: /** StephaneLenclud@31: */ StephaneLenclud@31: void MDM166AA::SetIconEmail(int aIndex, int aStatus) StephaneLenclud@31: { StephaneLenclud@32: if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconEmail]) StephaneLenclud@31: { StephaneLenclud@31: //Out of range StephaneLenclud@31: return; StephaneLenclud@31: } StephaneLenclud@31: StephaneLenclud@31: SendCommandSymbolControl((TIconId)(aIndex+EIconEnvelop),(aStatus==0?EIconOff:EIconOn)); StephaneLenclud@31: } StephaneLenclud@31: StephaneLenclud@31: /** StephaneLenclud@31: */ StephaneLenclud@31: void MDM166AA::SetIconMute(int aIndex, int aStatus) StephaneLenclud@31: { StephaneLenclud@32: if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconMute]) StephaneLenclud@31: { StephaneLenclud@31: //Out of range StephaneLenclud@31: return; StephaneLenclud@31: } StephaneLenclud@31: StephaneLenclud@31: SendCommandSymbolControl((TIconId)(aIndex+EIconMute),(aStatus==0?EIconOff:EIconOn)); StephaneLenclud@31: } StephaneLenclud@31: StephaneLenclud@31: /** StephaneLenclud@31: */ StephaneLenclud@31: void MDM166AA::SetIconVolume(int aIndex, int aStatus) StephaneLenclud@31: { StephaneLenclud@32: if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconVolume]) StephaneLenclud@31: { StephaneLenclud@31: //Out of range StephaneLenclud@31: return; StephaneLenclud@31: } StephaneLenclud@31: StephaneLenclud@32: if (aStatus=KSegmentsPerIcon[EMiniDisplayIconMute]) StephaneLenclud@31: { StephaneLenclud@31: //Out of range StephaneLenclud@31: return; StephaneLenclud@31: } StephaneLenclud@31: StephaneLenclud@32: SendCommandSymbolControl((TIconId)(aIndex+EIconVolumeLabel),(aStatus==0?EIconOff:EIconOn)); StephaneLenclud@31: } StephaneLenclud@31: StephaneLenclud@31: StephaneLenclud@31: /** StephaneLenclud@31: */ StephaneLenclud@31: void MDM166AA::SetIconPlay(int aIndex, int aStatus) StephaneLenclud@31: { StephaneLenclud@32: if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconPlay]) StephaneLenclud@31: { StephaneLenclud@31: //Out of range StephaneLenclud@31: return; StephaneLenclud@31: } StephaneLenclud@31: StephaneLenclud@31: SendCommandSymbolControl((TIconId)(aIndex+EIconPlay),(aStatus==0?EIconOff:EIconOn)); StephaneLenclud@31: } StephaneLenclud@31: StephaneLenclud@31: StephaneLenclud@31: /** StephaneLenclud@31: */ StephaneLenclud@31: void MDM166AA::SetIconPause(int aIndex, int aStatus) StephaneLenclud@31: { StephaneLenclud@32: if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconPause]) StephaneLenclud@31: { StephaneLenclud@31: //Out of range StephaneLenclud@31: return; StephaneLenclud@31: } StephaneLenclud@31: StephaneLenclud@31: SendCommandSymbolControl((TIconId)(aIndex+EIconPause),(aStatus==0?EIconOff:EIconOn)); StephaneLenclud@31: } StephaneLenclud@31: StephaneLenclud@31: StephaneLenclud@31: /** StephaneLenclud@31: */ StephaneLenclud@31: void MDM166AA::SetIconRecording(int aIndex, int aStatus) StephaneLenclud@31: { StephaneLenclud@32: if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconRecording]) StephaneLenclud@31: { StephaneLenclud@31: //Out of range StephaneLenclud@31: return; StephaneLenclud@31: } StephaneLenclud@31: StephaneLenclud@31: SendCommandSymbolControl((TIconId)(aIndex+EIconRecording),(aStatus==0?EIconOff:EIconOn)); StephaneLenclud@31: } StephaneLenclud@31: StephaneLenclud@31: /** StephaneLenclud@31: Set all our icons to the corresponding status. StephaneLenclud@31: */ StephaneLenclud@31: void MDM166AA::SetAllIcons(TIconStatus aStatus) StephaneLenclud@31: { StephaneLenclud@31: for (int i=EIconFirst;i<=EIconLast;i++) StephaneLenclud@31: { StephaneLenclud@31: SendCommandSymbolControl((TIconId)i,aStatus); StephaneLenclud@31: } StephaneLenclud@31: } StephaneLenclud@31: StephaneLenclud@31: /** StephaneLenclud@31: Symbols control StephaneLenclud@31: Segment On/Off and Grayscale/Brightness StephaneLenclud@31: [Code]1BH,30H,Ps,Pb StephaneLenclud@31: */ StephaneLenclud@31: void MDM166AA::SendCommandSymbolControl(TIconId aIconId, TIconStatus aStatus) StephaneLenclud@31: { StephaneLenclud@31: FutabaVfdReport report; StephaneLenclud@31: report[0]=0x00; //Report ID StephaneLenclud@31: report[1]=0x04; //Report size StephaneLenclud@31: report[2]=0x1B; //Command ID StephaneLenclud@31: report[3]=0x30; //Command ID StephaneLenclud@31: report[4]=aIconId; StephaneLenclud@31: report[5]=aStatus; StephaneLenclud@31: StephaneLenclud@31: Write(report); StephaneLenclud@31: } StephaneLenclud@31: StephaneLenclud@31: StephaneLenclud@31: /** StephaneLenclud@25: Clock setting StephaneLenclud@25: [Code]1BH,00H,Pm,Ph StephaneLenclud@25: [Function]Setting the clock data. The setting data is cleared, if the Reset command is input or power is turned off. StephaneLenclud@25: Ph = hour StephaneLenclud@25: Pm = minute StephaneLenclud@25: */ StephaneLenclud@28: void MDM166AA::SendCommandSetClockData(unsigned char aHour, unsigned char aMinute) StephaneLenclud@25: { StephaneLenclud@25: FutabaVfdReport report; StephaneLenclud@25: report[0]=0x00; //Report ID StephaneLenclud@25: report[1]=0x04; //Report size StephaneLenclud@25: report[2]=0x1B; //Command ID StephaneLenclud@25: report[3]=0x00; //Command ID StephaneLenclud@25: StephaneLenclud@25: //Minutes and Hours needs to be in hexadecimal view StephaneLenclud@25: //To get 21:59 you need to pass in 0x21:0x59 StephaneLenclud@25: //Weirdest format ever, I know StephaneLenclud@25: report[4]=(aMinute/10*16)+aMinute%10; StephaneLenclud@25: report[5]=(aHour/10*16)+aHour%10; StephaneLenclud@25: StephaneLenclud@25: Write(report); StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@28: Set display clock data according to local system time. StephaneLenclud@30: This will only provide 30s accuracy. StephaneLenclud@30: In fact display clock seconds are set to zero whenever clock data is set. StephaneLenclud@30: So you would only get second accuracy if this function was called when system time is at zero second. StephaneLenclud@30: It's the responsibility of AttemptClockSynchronization function to obtain second accuracy. StephaneLenclud@30: The present function is intended to provide only rough clock synchronization. StephaneLenclud@30: StephaneLenclud@30: @note Unfortunately this command also turns on clock display. StephaneLenclud@25: */ StephaneLenclud@28: void MDM166AA::SetClockData() StephaneLenclud@25: { StephaneLenclud@25: time_t rawtime; StephaneLenclud@25: struct tm * timeinfo; StephaneLenclud@25: StephaneLenclud@25: time ( &rawtime ); StephaneLenclud@25: timeinfo = localtime ( &rawtime ); StephaneLenclud@28: //Adjust minute as best as we can so that we have a 30 seconds offset at most rather a than a full minute. StephaneLenclud@28: if (timeinfo->tm_sec>30) StephaneLenclud@28: { StephaneLenclud@28: //Use the next minute then StephaneLenclud@28: timeinfo->tm_min++; StephaneLenclud@28: if (timeinfo->tm_min==60) StephaneLenclud@28: { StephaneLenclud@28: //Use the next hour then StephaneLenclud@28: timeinfo->tm_hour++; StephaneLenclud@28: timeinfo->tm_min=0; StephaneLenclud@28: if (timeinfo->tm_hour==24) StephaneLenclud@28: { StephaneLenclud@28: //Move to the next day then StephaneLenclud@28: timeinfo->tm_hour=0; StephaneLenclud@28: } StephaneLenclud@28: } StephaneLenclud@28: } StephaneLenclud@28: StephaneLenclud@28: //Send hours and minutes to our display StephaneLenclud@28: SendCommandSetClockData(timeinfo->tm_hour,timeinfo->tm_min); StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@25: StephaneLenclud@25: /** StephaneLenclud@25: Clock display StephaneLenclud@25: [Code] 1BH,Ps,aL,aH,Pf StephaneLenclud@25: [Function] Clock is displayed small or big. StephaneLenclud@25: */ StephaneLenclud@25: void MDM166AA::SendCommandClockDisplay(TClockSize aClockSize, TClockFormat aClockFormat) StephaneLenclud@25: { StephaneLenclud@25: FutabaVfdReport report; StephaneLenclud@25: report[0]=0x00; //Report ID StephaneLenclud@25: report[1]=0x03; //Report size StephaneLenclud@25: report[2]=0x1B; //Command ID StephaneLenclud@25: report[3]=aClockSize; // StephaneLenclud@25: report[4]=aClockFormat; // StephaneLenclud@25: StephaneLenclud@25: Write(report); StephaneLenclud@25: } StephaneLenclud@25: StephaneLenclud@27: StephaneLenclud@27: /** StephaneLenclud@27: Display RAM filled with 00H. StephaneLenclud@27: Address Counter is set by 00H. StephaneLenclud@27: Dimming is set to 50%. StephaneLenclud@31: Turn off all icons segments. StephaneLenclud@34: StephaneLenclud@34: This does not reset our clock settings. StephaneLenclud@27: */ StephaneLenclud@27: void MDM166AA::SendCommandReset() StephaneLenclud@27: { StephaneLenclud@27: FutabaVfdReport report; StephaneLenclud@27: report[0]=0x00; //Report ID StephaneLenclud@27: report[1]=0x01; //Report length. StephaneLenclud@27: report[2]=0x1F; //Command ID StephaneLenclud@27: Write(report); StephaneLenclud@27: } StephaneLenclud@27: StephaneLenclud@27: StephaneLenclud@27: /** StephaneLenclud@27: Set Address Counter (AC) values: 1BH + 60H + xxH StephaneLenclud@27: xxH: 00 ~ BFH StephaneLenclud@27: AC value represents the start address for graphic data. StephaneLenclud@27: There are 192 bytes as display RAM. It can be set on anywhere even if AC value is not visible area. StephaneLenclud@27: The default value is 00H. StephaneLenclud@27: Default: 00H StephaneLenclud@27: When clock is displayed, AC value is set 00H. StephaneLenclud@27: */ StephaneLenclud@27: void MDM166AA::SendCommandSetAddressCounter(unsigned char aAddressCounter) StephaneLenclud@27: { StephaneLenclud@27: FutabaVfdReport report; StephaneLenclud@27: report[0]=0x00; //Report ID StephaneLenclud@27: report[1]=0x03; //Report length. StephaneLenclud@27: report[2]=0x1B; //Command ID StephaneLenclud@27: report[3]=0x60; //Command ID StephaneLenclud@27: report[4]=aAddressCounter; StephaneLenclud@27: Write(report); StephaneLenclud@27: } StephaneLenclud@27: StephaneLenclud@27: StephaneLenclud@27: /** StephaneLenclud@27: Set the defined pixel block to the given value. StephaneLenclud@27: StephaneLenclud@27: @param The size of our pixel data. Number of pixels divided by 8. StephaneLenclud@27: @param Pointer to our pixel data. StephaneLenclud@27: */ StephaneLenclud@27: void MDM166AA::SendCommandWriteGraphicData(int aSize, unsigned char* aPixels) StephaneLenclud@27: { StephaneLenclud@27: //TODO: Remove that at some point StephaneLenclud@27: SendCommandSetAddressCounter(0); StephaneLenclud@27: StephaneLenclud@27: const int KMaxPixelBytes=48; StephaneLenclud@27: const int KHeaderSize=3; StephaneLenclud@27: StephaneLenclud@27: int remainingSize=aSize; StephaneLenclud@27: int sizeWritten=0; StephaneLenclud@27: StephaneLenclud@27: while (remainingSize>0) StephaneLenclud@27: { StephaneLenclud@27: //Only send a maximum of 48 bytes worth of pixels per report StephaneLenclud@27: const int KPixelDataSize=(remainingSize<=KMaxPixelBytes?remainingSize:KMaxPixelBytes); StephaneLenclud@27: StephaneLenclud@27: FutabaVfdReport report; StephaneLenclud@27: report[0]=0x00; //Report ID StephaneLenclud@27: report[1]=KPixelDataSize+KHeaderSize; //Report length. +3 is for our header first 3 bytes. StephaneLenclud@27: report[2]=0x1B; //Command ID StephaneLenclud@27: report[3]=0x70; //Command ID StephaneLenclud@27: report[4]=KPixelDataSize; //Size of pixel data in bytes StephaneLenclud@27: memcpy(report.Buffer()+5, aPixels+sizeWritten, KPixelDataSize); StephaneLenclud@27: Write(report); StephaneLenclud@27: //Advance StephaneLenclud@27: sizeWritten+=KPixelDataSize; StephaneLenclud@27: remainingSize-=KPixelDataSize; StephaneLenclud@27: } StephaneLenclud@27: }