/**
 * @fileOverview LPC1769のPwmペリフェラル、ピンを制御するクラスを定義する。
 */
(function(){
var DEV=LPC1769;
var BCF=DEV._BCF;
var EE=DEV._EE;
var isUndef=MiMicLib.isUndef;
var cloneAssoc=MiMicLib.cloneAssoc;



/**
 * LPCXPresso1769.Pwm (Pwm)クラスのコンストラクタ。
 * MCUに関連付けしたPwmペリフェラルを生成する。
 * Pwmクラスは、PWM1ペリフェラルを管理して、PWMペリフェラル全体の設定を管理する。
 * Pwmの分解能は、無指定の場合25MHz(1/4クロック)設定である。関数は、PWMのマッチレジスタ以外に、PCR,CTCR,TCRを同時に初期化する。
 * @name LPC1769.Pwm
 * @constructor
 * @param {object as LPC1769.Mcu} i_mcu
 * インスタンスを結びつけるMcuオブジェクト。
 * @param {aobject as associative array} i_opt
 * インスタンス生成と同時にsetOpt関数でセットする値を指定する。省略時は、{freq:100,phl:{power:1,clock:0}}である。
 * 詳細は、setOpt関数を参照。 
 * @example
 * //create PWM  pheripheral
 * var mcu=new LPC1769.Mcu("192.168.0.39");
 * var pwm=new LPC1769.Pwm(mcu,"");
 */
DEV.Pwm=function Pwm(i_mcu,i_opt)
{
	try{
		this._mcu=i_mcu;
		//PHL生成。
		this._phl=new DEV.Peripheral(i_mcu,DEV.PHL.PWM1);
		//設定値のロード
		var opt=isUndef(i_opt)?{phl:{}}:
		{//i_optある。
			phl:isUndef(i_opt.phl)?{}:cloneAssoc(i_opt.phl),
			freq:i_opt.freq
		};
		//デフォルト値設定
		if(isUndef(opt.phl.power)){opt.phl.power=1;};
		if(isUndef(opt.phl.clock)){opt.phl.clock=0;};
		if(isUndef(opt.freq)){opt.freq=100;}
		
		var bc="";
		var db=new Array();
		//optionの設定
		bc+=this.BCF_setOpt(opt,db);
		bc+=BCF.setMem(this._PC,0x0,db);
		//PCR
		bc+=BCF.setMem(this._PCR,0x0,db);
		//CTCR
		bc+=BCF.setMem(this._CTCR,0x0,db);
		//TCRに値設定(カウンタとPWMモードを無効化)
		bc+=BCF.setMem(this._TCR,0x0,db);
		bc+=BCF.setMem(this._TC,0x0,db);
		//TCRに値設定(カウンタとPWMモードを有効化)
		bc+=BCF.setMem(this._TCR,0x9,db);
		//初期化
		this._mcu.callMiMicWithCheck(bc+BCF.END,db);
		//ペリフェラルをMCUに登録
		this._mcu.registerPhl(this,"PWM");
	}catch(e){
		throw new MiMicException(e);
	}

}
DEV.Pwm.prototype=
{
	_cache:{
		mr0:0
	},
	_TCR:0x40018004,
	_TC:0x40018008,
	_PR:0x4001800C,
	_PC:0x40018010,
	_MCR:0x40018018,
	_PCR:0x4001804C,
	_LER:0x40018050,
	_CTCR:0x40018070,
	_MRn:[0x40018018,0x4001801C,0x40018020,0x40018024,0x40018040,0x40018044,0x40018048],
	_phl:null,
	_mcu:null,

	/**
	 * @private
	 */
	BCF_setOpt:function BCF_setOpt(i_opt,i_db)
	{
		try{
			var bc="";
			var mr0;
			//ペリフェラルの設定
			if(!isUndef(i_opt.phl)){
				bc+=this._phl.BCF_setOpt(i_opt.phl,i_db);
			}
			if(!isUndef(i_opt.freq)){
				//サイクル(Hz)とペリフェラルのクロックから、MR0のサイクルを決定
				mr0=Math.round(this._phl.getPCLK()/i_opt.freq);
				if(mr0<=0){
					//ペリフェラルクロック小さすぎﾜﾛﾀ
					throw new MiMicException("The peripheral clock too small.");
				}
				//MR0に値設定
				bc+=this.BCF_setMRn(0,mr0,i_db);
				//LERにビットセット
				bc+=this.BCF_setLER(0,i_db);
				this._cache.mr0=mr0;
			}
			return bc;
		}catch(e){
			throw new MiMicException(e);
		}
	},
	BCF_setLER:function BCF_setLER(i_ch,i_db)
	{
		try{
			//LERにビットをセット
			return BCF.setBit(this._LER,0x01,0x01,i_ch,i_db);
		}catch(e){
			throw new MiMicException(e);
		}
	},
	BCF_setLERs:function BCF_setLERs(i_mask,i_value,i_db)
	{
		try{
			//LERにビットをセット
			return BCF.setBit(this._LER,i_mask,i_value*i_mask,0,i_db);
		}catch(e){
			throw new MiMicException(e);
		}
	},
	
	/**
	 * MRnに値を設定するBC
	 @private
	 */
	BCF_setMRn:function BCF_setMRn(i_ch,i_val,i_db)
	{
		try{
			return BCF.setMem(this._MRn[i_ch],i_val,i_db);
		}catch(e){
			throw new MiMicException(e);
		}
	},
	BCF_setMRnByDuty:function BCF_setMRnByDuty(i_ch,i_duty,i_db)
	{
		try{
			return this.BCF_setMRn(i_ch,Math.round(i_duty*this._cache.mr0),i_db);
		}catch(e){
			throw new MiMicException(e);
		}
	},
	/**
	 * PCRに値を設定するBC
	 i_ch=1の場合、エッジモードは無視します。
	 @private
	 @param i_mask
	 @param i_edge
	 エッジモード(1/0)
	 @param i_en
	有効/無効の選択
	 */
	BCF_setPCRbits:function BCF_setPCRbits(i_mask,i_edge,i_en,i_db)
	{
		try{
			var m,v;
			m=v=0;
			if(!isUndef(i_edge)){
				var t=0xff&i_mask;
				v|=i_edge*t;
				m|=t;
			}
			if(!isUndef(i_en)){
				var t=0xff00&i_mask;
				v|=(i_en*t);
				m|=t;
			}
			//ビットの設定
			return BCF.setBit(this._PCR,m,v,0,i_db);
		}catch(e){
			throw new MiMicException(e);
		}
	},
	
	/**
	 * Pwmペリフェラルに値を設定する。
	 * 設定する値は、全てのPWMチャンネル（PIN）に共通な項目である。
	 * @name LPC1769.Pwm#setOpt
	 * @function
	 * @param {object as associative array} i_opt
	 * GPIOピンのコンフィグレーションパラメタである。必要な値を格納した連想配列で指定する。
	 * 全ての値を省略することは出来ない。連想配列のメンバは以下の通り。
	 * <pre>{freq:int,phl:object as associative array}</pre>
	 * <ul>
	 * <li>freq - PWMの基本周波数(Hz)である。この値は、全てのPWMチャンネルで共通である。</li>
	 * <li>phl - ペリフェラルの共通パラメタである。LPC1769.Peripheral.setOpt関数のi_optに渡すパラメタである。詳細はLPC1769.Peripheral#setOptを参照。</li>
	 * </ul>
	 * @example
	 * //create PWM (logical)peripheral
	 * var mcu=new LPC1769.Mcu(“192.168.0.39”);
	 * var pwm=new LPC1769.Pwm(mcu);
	 * pwm.setOpt({freq:1000});
	 */
	setOpt:function setOpt(i_opt)
	{
		try{
			var db=new Array();
			var bc=this.BCF_setOpt(i_opt,db);
			this._mcu.callMiMicWithCheck(bc+BCF.END,db);
		}catch(e){
			throw new MiMicException(e);
		}
	},	
	/**
	 * PWM機能を持つピンを取得する。
	 * ピン識別子で指定されるピンをPWMペリフェラルと結合して、PwmPinを生成する。
	 * 関数は、PwmPinオブジェクトのコンストラクタをコールして、PwmPinを生成する。失敗すると、例外をスローする。
	 * 生成ルールについての詳細は、PwmPinを参照。
	 * @name LPC1769.Pwm#getPin
	 * @function
	 * @param {object as ピン識別子} i_pin
	 * PWM機能を割り当てるPINの識別子である。
	 * @param {object as associative array} i_opt
	 * PemPinのコンストラクタに渡すオプション値。省略時は{duty:0,enable:1,pin:{sel:auto}}である。
	 * @return {object as LPC1769.PwmPin}
	 * LPC1769.PwmPinクラスのオブジェクトである。
	 * @example
	 * //create 100hz d=0.5 pwm
	 * var mcu=new LPC1769.Mcu("192.168.0.39");
	 * var pwm=new LPC1769.Pwm(mcu);
	 * var pwmpin=pwm.getPin(LPC1769.P2[0],{duty:0.5});
	 */
	getPin:function getPin(i_pin,i_opt)
	{
		try{
			return new DEV.PwmPin(this,i_pin,i_opt);
		}catch(e){
			throw new MiMicException(e);
		}
	},
	/**
	 * PWM機能を持つポート(Pin集合)を取得する。
	 * ピン識別子で指定されるピンのセットをPWMペリフェラルと結合して、PwmPortを生成する。
	 * 関数は、PwmPortのコンストラクタをコールする。
	 * 生成ルールについては、PwmPort関数を参照すること。
	 * @name LPC1769.Pwm#getPort
	 * @function
	 * @param {array[ピン識別子]} i_pin
	 * Pwm機能を割り当てるPINの識別子の配列である。値は、LPC1769.Pn[m]のメンバ変数である。
	 * @param {object as associative array} i_opt
	 * AdcPortのコンストラクタに渡すオプション値。省略時はundefinedである。詳細はLPC1769.PwmPort#setOptを参照。
	 * @return {object as LPC1769.PwmPort}
	 * LPC1769.PwmPortクラスのオブジェクトである。
	 * @example
	 * //create 2AdcPort that has 2 pins.
	 * var mcu=new LPC1769.Mcu("192.168.0.39");
	 * var pwm=new LPC1769.Pwm(mcu);
	 * var port=pwm.getPort([LPC1769.P0[23],LPC1769.P0[24]]);
	 */
	getPort:function getPort(i_pins,i_opt)
	{
		try{
			return new DEV.PwmPort(this,i_pins,i_opt);
		}catch(e){
			throw new MiMicException(e);
		}
	}	
}

/**
 * Pwm pinからPwmInfoを取得
 * @private
 */
function pin2PwmPinInfo(i_pin)
{
	try{
		//pinの完全な機能名を得る。(得られれば機能がある。)
		var fn=DEV.completePinFunctionName(i_pin,"PWM");
		//portとbitを得る(AD0だけしか管理しないよ)
		var a=fn.substring(2).split(".");
		var r={port:0,ch:parseInt(a[1]),pin_sel:DEV.getPinSelByFunctionName(i_pin,fn)};
		if(!isNaN(r.ch)){
			return r;
		}
		throw new MiMicException(EE.INVALID_CFG,"The pin has not PWM fuction.");
	}catch(e){
		throw new MiMicException(e);	
	}
}
/**
 * Pwmペリフェラルと複数のPin識別子から、シングルエッジPwmポートを構成する。
 * Pwmペリフェラルオブジェクトにピン識別子の配列で指定されたピンを関連付けて、PWM機能を持つポートを生成する。
 * 関数は、ピン識別子を元に、それぞれのピンがPWM機能に接続できるかを調べる。
 * ピンにPWM機能を割り当てられない場合、例外が発生する。どのピンにPWM機能が割り当てられるかは、MCUのスペックシートを参照すること。
 * PWMポートは、複数の同一周期のPWMピンのデューティ比を、同期して操作するときに便利である。
 * @constructor
 * @name LPC1769.PwmPort
 * @param {object as LPC1769.Pwm} i_pwm
 * インスタンスを結びつけるPwmオブジェクト。
 * @param {array[pin識別子]} i_pins
 * ピン識別子の配列。指定できるのは、LPC1769.P?[?]である。
 * @param {object as associative array} i_opt
 * setOpt関数のi_optに渡すパラメタである。省略可能。省略時は{duty:100,enable:1,pin{sel:auto}}を設定する。
 * autoは、関数が自動的に決定するPINSELの値である。詳細はsetOpt関数を参照。 
 * dutyをピン毎に設定する場合は、setDuty関数を使うこと。
 * @example
 * //create PWM1.0,PWM1.1
 * var mcu=new LPC1769.Mcu(“192.168.0.39”);
 * var pwm=new LPC1769.Pwm(mcu,{freq:100});
 * var port=new LPC1769.PwmPort(pwm,[LPC1769.P2[0],LPC1769.P2[1]]); 
 */
DEV.PwmPort=function PwmPort(i_pwm,i_pins,i_opt)
{
	try{
		this._pwm=i_pwm;
		//ピンセットを取得
		this._pins=new Array();
		//pinに変換する。
		for(var i=0;i<i_pins.length;i++){
			this._pins.push(pin2PwmPinInfo(i_pins[i]));
		}
		//pinが全て同じポートに所属しているか確認
		var p=this._pins[0].port;
		for(var i=1;i<this._pins.length;i++){
			if(p!=this._pins[i].port){
				throw new MiMicException("Invalid pin combination.");
			}
		}
		//ポートの生成
		this._port=new DEV.Port(i_pwm._mcu,i_pins);
		this._port_no=p;
		
		this._ler_mask=0;
		this._pcr_mask=0;
		for(var i=0;i<this._pins.length;i++){
			this._ler_mask|=(0x1<<this._pins[i].ch);
			this._pcr_mask|=(0x101<<this._pins[i].ch);
		}		
		var opt=isUndef(i_opt)?{pin:{}}:
		{
			pin:isUndef(i_opt.pin)?{}:cloneAssoc(i_opt.pin),
			duty:i_opt.duty,
			enable:i_opt.enable
		};
		//デフォルト値のロード
		if(isUndef(opt.pin.sel)){opt.pin.sel=this._PINSEL_AUTO_DETECT;}
		if(isUndef(opt.duty)){opt.duty=0;}
		if(isUndef(opt.enable)){opt.enable=1;}
		this.setOpt(opt);
	}catch(e){
		throw new MiMicException(e);
	}	
}
DEV.PwmPort.prototype=
{
	_PINSEL_AUTO_DETECT:0x0fffffff,
	_pwm:null,
	_pins:null,
	_port_no:0,
	_port:null,
	_pcr_mask:0,
	_ler_mask:0,
	/**
	 * PWMポートにオプション値を設定する。
	 * 関数は、ポートを構成する全てのピンに、同じ値を設定する。
	 * 設定可能な値は、LPC1769.PwmPin#setOptと同じである。	 
	 * @name LPC1769.PwmPort#setOpt
	 * @function
	 * @param {object as associative array} i_opt
	 * LPC1769.PwmPin#setOptを参照。
	 * @example
	 * var mcu=new LPC1769.Mcu("192.168.128.39",true);
	 * var pwm=mcu.getPeripheral("PWM",{freq:100});
	 * var port=pwm.getPort([LPC1769.P2[0]]);
	 * port.setOpt({enable:0});
	 */
	setOpt:function setOpt(i_opt)
	{
		try{
			var db=new Array();
			//BCの生成
			var bc="";
			//i_optの展開
			if(!isUndef(i_opt.pin)){
				var optset=new Array();
				for(var i=0;i<this._pins.length;i++){
					//pinselが_PINSEL_AUTO_DETECTならばauto。そうでなければundefinedも含めて設定
					var s=(i_opt.pin.sel==this._PINSEL_AUTO_DETECT)?this._pins[i].pin_sel:i_opt.pin.sel;
					optset.push({sel:s,mode:i_opt.pin.mode,od:i_opt.pin.od});
				}
				//portの設定
				bc+=this._port.BCF_setOpts(optset,db);
			}
			//PCRに値設定
			if(!isUndef(i_opt.enable)){
				bc+=this._pwm.BCF_setPCRbits(this._pcr_mask,0,i_opt.enable,db);
			}
			//デューティ比を
			if(!isUndef(i_opt.duty)){
				//デューティ比を設定
				for(var i=0;i<this._pins.length;i++){
					bc+=this._pwm.BCF_setMRnByDuty(this._pins[i].ch,i_opt.duty,db);
				}
				//LERにセット
				bc+=this._pwm.BCF_setLERs(this._ler_mask,1,db);
			}
			this._pwm._mcu.callMiMicWithCheck(bc+BCF.END,db);
			return;
		}catch(e){
			throw new MiMicException(e);
		}
	},
	/**
	 * PWMポートのデューティ比をまとめて変更する。
	 * @name LPC1769.PwmPort#setDutys
	 * @function
	 * @param {array[double]} i_duty_array
	 * デューティ比の配列である。0.0<=n<=1.0のdouble値を指定する。
	 * ポートを構成するピンの数だけ指定する。変更しないピンには、undefinedを指定すること。
	 * @example
	 * varmcu=new LPC1769.Mcu("192.168.128.39",true);
	 * var pwm=mcu.getPeripheral("PWM",{freq:100});
	 * var port=pwm.getPort([LPC1769.P2[0],LPC1769.P2[1]]);
	 * pwm.setDutys([0.5,0.0]);//PWM0=0.5,PWM1=0.0
	 * pwm.setDutys([undefined,0.0]);//PWM1=0.0
	 */	
	setDutys:function setDutys(i_duty_array)
	{
		try{
			if(i_duty_array.length!=this._pins.length){
				throw new MiMicException();
			}
			var bc="";
			var db=new Array();
			//デューティ比をまとめてセット
			for(var i=0;i<this._pins.length;i++){
				if(!isUndef(i_duty_array[i])){
					bc+=this._pwm.BCF_setMRnByDuty(this._pins[i].ch,i_duty_array[i],db);
				}
			}
			//LERにセット
			bc+=this._pwm.BCF_setLERs(this._ler_mask,1,db);
			this._pwm._mcu.callMiMicWithCheck(bc+BCF.END,db);
		}catch(e){
			throw new MiMicException(e);
		}
	}
}
/**
 * PwmペリフェラルとPin識別子から、シングルエッジPwmピンを構成する。
 * Pwmペリフェラルオブジェクトにピン識別子で指定されたピンを関連付けて、PWM機能を持つピンを生成する。
 * 関数は、ピン識別子を元に、そのピンがPWM機能に接続できるかを調べる。ピンにPWM機能を割り当てられない場合、例外が発生する。どのピンにPWM機能が割り当てられるかは、MCUのスペックシートを参照すること。
 * ピンがPWM機能を持たない場合、例外が発生する。
 * @constructor
 * @name LPC1769.PwmPin
 * @param {object as LPC1769.Pwm} i_pwm
 * インスタンスを結びつけるAdcオブジェクト。
 * @param {object as pin識別子} i_pin
 * ピン識別子。指定できるのは、LPC1769.P?[?]である。
 * @param {object as associative array} i_opt
 * setOpt関数のi_optに渡すパラメタである。省略可能。省略時は{duty:100,enable:1,pin{sel:auto}}を設定する。
 * autoは、関数が自動的に決定するPINSELの値である。詳細はsetOpt関数を参照。 
 * @example
 * //create PWM1.0
 * var mcu=new LPC1769.Mcu(“192.168.0.39”);
 * var pwm=new LPC1769.Pwm(mcu,{freq:100});
 * var pwmpin=new LPC1769.PwmPin(pwm,P2[0]); 
 */
DEV.PwmPin=function PwmPin(i_pwm,i_pin,i_opt)
{
	try{
		//1pinのポートとして実装
		this._pport=new DEV.PwmPort(i_pwm,[i_pin],i_opt);
	}catch(e){
		throw new MiMicException(e);
	}
}

DEV.PwmPin.prototype=
{
	_pport:null,

	/**
	 * PWMピンにオプション値を設定する。
	 * @name LPC1769.PwmPin#setOpt
	 * @function
	 * @param {object as associative array} i_opt
	 * PWMピンのコンフィグレーションパラメタである。必要な値を格納した連想配列で指定する。
	 * 全ての値を省略することは出来ない。連想配列のメンバは以下の通り。
	 * <pre>{pin:{associative array},enable:int,duty:double}</pre>
	 * <ul>
	 * <li>pin - LPC1769.Pin#setOpt関数のi_optに渡すパラメタである。</li>
	 * <li>enable - PWMの有効/無効を切り替える。1で有効。</li>
	 * <li>duty - デューティ比である。0.0<=n<=1.0のdouble値を指定する。</li>
	 * </ul>
	 * @example
	 * var mcu=new LPC1769.Mcu("192.168.128.39",true);
	 * var pwm=mcu.getPeripheral("PWM",{freq:100});
	 * var pin=pwm.getPin(LPC1769.P2[0]);
	 * pin.setOpt({duty:0});
	 */
	setOpt:function setOpt(i_opt)
	{
		try{
			this._pport.setOpt(i_opt);
		}catch(e){
			throw new MiMicException(e);
		}
	},
	/**
	 * PWMのデューティ比を変更する。
	 * @name LPC1769.PwmPin#setDuty
	 * @function
	 * @param {double} i_duty
	 * デューティ比である。0.0<=n<=1.0のdouble値を指定する。
	 * @example
	 * varmcu=new LPC1769.Mcu("192.168.128.39",true);
	 * var pwm=mcu.getPeripheral("PWM",{freq:100});
	 * pwmpin=pwm.getPin(LPC1769.P2[0]);
	 * pwmpin.setDuty(0.5);
	 */
	setDuty:function setDuty(i_duty)
	{
		try{
			this._pport.setDutys([i_duty]);
		}catch(e){
			throw new MiMicException(e);
		}
	}
}
	


}());
