//----------------------------------------------------------------------
//	ファンクション･ジェネレータ (Nucleo-F446RE 用)
//	COM ポートの自動検出に対応（9600 baud）
//
//	設定できる項目
//		波形の種類：  正弦波，方形波，合成方形波（フーリエ級数の５倍波までの和）
//		振幅：		   0.00 ～ 1.00 倍
//		周波数：	   10 Hz ～ 10 kHz
//		ノイズ付加の有無
//	標本化間隔：2.5 μs
//	使用タイマ：TIM7
//	信号出力のピン：        A2
//  同期信号出力のピン：    A5
//
//	PC 側のプログラム
//		CQ_FunctionGenerator.exe
//
//  2023/01/17, Copyright (c) 2023 MIKAMI, Naoki
//----------------------------------------------------------------------

#include "F446_DAC.hpp"			// DA 変換器用
#include "SerialRxTxIntr.hpp"	// シリアル通信用
#include "MyTicker7.hpp"		// タイマ用
#include "FastSin.hpp"			// 高速低精度 sin 関数
#include "MSeq16.hpp"			// ノイズ発生器で使う M 系列信号発生器
#include "IirCascade.hpp"		// ノイズ発生器で使う低域通過フィルタ
#include "CoefficientsLp4.hpp"	// 低域通過フィルタの係数
#include  <cctype>				// isalpha() で使用
using namespace Mikami;

#ifndef __STM32F446xx_H
#error "Use Nucleo-F446RE"
#endif

const float T0_ = 2.5f;			// 出力の標本化間隔： 2.5 μs
const float C0_ = 4.0f;
const float C0_2_ = C0_/2.0f;
const float C0T0_ = C0_*T0_*1.0e-6f;

MyTicker7 timer_(T0_);			// タイマ割り込み用クラスのオブジェクト，TIM7 を利用
DigitalOut sync_(A5);			// 同期信号出力用

float phi_ = 0;
float dPhi_ = C0T0_*1000;		// 周波数決める変数，開始時は 1 kHz;
float volume_ = 0.9f*0.5f;		// 出力の振幅を決める変数，開始時は 0.45
float volNoise_ = 0.5f;			// ノイズの大きさを決める変数

// DA 変換器に関する関数等
DacF446 dac_;					// DA 変換器オブジェクト
void DacOut(float x) { dac_.Write(x); }		// 引数の値を出力
void DacZero(float x) { dac_.Write(0.0f); } // 0 を出力
void (*fpDa)(float) = DacZero;	// 起動時は 0 を出力

// ノイズ付加に関する関数等
MSeq16 mSeq_;					// M 系列発生器
IirCascade filter_(ORDER_, hk_, G0_);	// 低域通過フィルタ
float Noise() { return volNoise_*filter_.Execute(mSeq_.Execute()); }
float NoiseFree() { return 0; }
float (*fpN)() = NoiseFree;		// 起動時はノイズなし 

// 発生する信号を定義する関数
// 正弦波
float Sin(float sinx) { return volume_*sinx + fpN(); }
// 方形波
float Rect(float sinx)
{
	float x = (sinx >= 0) ? volume_ : -volume_;
	return x + fpN();
}
// 合成方形波（５倍波まで）
float Syn(float sinx)
{
	static const float ONE_3 = 1.0f/3.0f;	// フーリエ合成で使用
	static const float ONE_5 = 0.2f;		// フーリエ合成で使用

	float sinx2 = sinx*sinx;
	float sin3x = (-4.0f*sinx2 + 3.0f)*sinx;
	float sin5x = ((16.0f*sinx2 - 20.0f)*sinx2 + 5.0f)*sinx;

	return volume_*(sinx + ONE_3*sin3x + ONE_5*sin5x) + fpN();
}
float (*fpS)(float) = Sin;		// 起動時は正弦波

// ラジオボタン，チェックボックスに対応する処理
void Select(string str)
{
	if (str == "On")	fpDa = DacOut;		// 選択された信号の出力
	if (str == "Off")	fpDa = DacZero;		// 0 を出力

	if (str == "Sin")	fpS = Sin;			// 正弦波
	if (str == "Rect")	fpS = Rect;			// 方形波
	if (str == "Syn")	fpS = Syn;			// 合成方形波

	if (str == "NsOn")	fpN = Noise;		// ノイズ付加
	if (str == "NsOff") fpN = NoiseFree;	// ノイズなし
}

// スライダ（TrackBar）に対応する処理
void NumericCtrl(string str)
{
	char c1 = str[0];	// 先頭の文字を取得
	float x = atof(str.substr(1).c_str());

	if (c1 == '#') volume_ = x*0.9f;	// 出力振幅の変更
	if (c1 == '$') dPhi_ = C0T0_*x;		// 周波数の変更
	if (c1 == '%') volNoise_ = x;		// ノイズの大きさの変更
}

// タイマ割り込みに対する割込みサービス･ルーチン
void TimerIsr()
{
	float sinx = FastSin(phi_); // 基本波発生
	fpDa(fpS(sinx));			// 指定された信号を出力
	GPIOC->BSRR = (sinx >= 0) ?	// 同期信号を出力
				  0x1 : 0x10000;

	phi_ += dPhi_;
	if (phi_ >= C0_2_) phi_ -= C0_; // オーバーフロー防止
}

int main()
{
	SerialRxTxIntr com;				// PC との通信用，9600 baud
	// 以下の割り込み優先順位の設定を忘れないこと
	NVIC_SetPriority(TIM7_IRQn, 0);		// 最優先
	NVIC_SetPriority(USART2_IRQn, 1);	// USART2 割り込み：次に優先

	timer_.Attach(&TimerIsr);	// タイマ割り込み設定

	while (true)	// PC からの指令に対応する処理
	{
		string str = com.WaitGetBuffer();
        if (str == "FG")
            com.TxString("ACK\n");     // PC からの "FG" に対して "ACK" を送信する
        else
        {
            if (isalpha(str[0]))	// 先頭が A ～ Z, a ～ z の場合
			    Select(str);
            else                	// 先頭が A ～ Z, a ～ z 以外の場合
			    NumericCtrl(str);
        }
	}
}
