/****************************************************************************************
 * KF8A系列单片机	KF8A100GQT_DEMO	开发板演示程序
 * 标    题: IIC主机、从机实验
 * 项目名: 15- IIC_Sample
 * 开发环境：ChipON IDE
 * 版          本     ：V1.0 (2019/07/23)
 * 作 者: 上海芯旺微电子有限公司
 * 功能简述:	测试IIC的主从机模式
 ****************************************************************************************/
#include<KF8A100GQT.h>
#include "includes.h"

s_uint8 SSCI_I2C_State;

// 复用i2c样例的  结构体变量对象，因样例的 配置代码使用10bit或7bit分支，故定义在i2c.h文件中，这里仅注释直观化
#define  isSampleMode10bits			SSCII2C_DateStruct.flag.bit0  // 样例配置是否为10bit通信
#define  isSampleModeMaster			SSCII2C_DateStruct.flag.bit1  // 样例配置是否工作在主机
#define  Mode10bitsAddrUAState		SSCII2C_DateStruct.flag.bit7  // 样例10bit要更新地址的，更新谁状态机
#define  isSampleRevCommandAddr		SSCII2C_DateStruct.flag.bit6  // 样例协议的标准EEprom协议的接收8bit地址或命令标志


void delay_ms(uint16 num)
{
	uint16 i,j;
	for(i=0;i<num;i++)
	{
		j=1000;
		while(j--);
	}
}


void SYS_Init(void)
{
	//使用外部晶振时，引脚配置输入，配置字选择外部时钟且允许切换
	SCLKCTL = SYSYCLK_SEL_SCKS|SYSCLK_SCLK_SEL_INTHF|SYSCLK_FREQ_DIV1;	//INTHF作为主时钟
//	_NOP();_NOP();	//高速时钟稳定
//	PLLEN=0;
//	PLLCTL = PLL_SEL_DIV1|PLL_SEL_INTHF|PLLBIAS_CUR_MIN|PLLPMUL_2;
//	PLLEN=1;
//	delay_ms(200);
//	SCLKCTL|=SYSYCLK_SEL_SCKS|SYSCLK_SCLK_SEL_UPCK;		//UPCK作为主时钟	32M
//	SCLKOE = 0;		//机器周期禁能输出,输出波形与IO翻转速率相关
//	CLKOUTSEL = 0;
	HFCKCTL = 0xCE;	//外设时钟开启,选择INTHF为HFCLK时钟信号

	TR0 = 0;	TR1	= 0;	TR2	= 0;	TR3 = 0;
	TR4 = 0;	TR5 = 0;	TR6 = 0;	TR7 = 0;
	TR9 = 0;	TRA = 0;	TRB = 0;
	P0LR = 0;	P1LR = 0;	P2LR = 0;	P3LR = 0;
	P4LR = 0;	P5LR = 0;	P6LR = 0;	P7LR = 0;
	P9LR = 0;	PALR = 0;	PBLR = 0;
}

/***********************************************************************************
 * 函数名     	：
 * 函数功能	：有限周期数的延迟
 * 入口参数	：1~255的有效范围
 * 返回         ：无
 ***********************************************************************************
 */
void Delay_Wait_CLK_Sample(unsigned char x)
{
#pragma save
#pragma disable_warning 59
#pragma disable_warning 85   // 59 85 屏蔽参数未使用警告，函数未返回警告，作用于嵌汇编函数功能实现

	__asm
	 ;// 参数使用R0传递
	 ;//MOV R0,#0x05 固定样例
	 DECJZ R0
	 JMP $-1
	__endasm;

#pragma restore
}
/***********************************************************************************
 * 函数名     	：I2C_Slave_ISR_Server
 * 函数功能	：从模式的设备应答实现协议控制
 * 入口参数	：无
 * 返回         ：无
 *
 **********************************************************************************/
void I2C_Slave_ISR_Server(void)
{
	uint8 TempDate;
	// 从机模式中断标志，进行过程处理，
	// 如果简单应用，主机可以直接读模式的设备地址后，直接就是读取数据。
	// 简单应用，即主机读寻址设备，从机准备默认的一个数据。主机读获取从机的一个数据，如果数据较多，继续读取。
	// 这里的demo逻辑遵从EEprom的过程。

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//内容读出，可能是地址，可能是数据，不读取则会造成接收溢出
	TempDate=SSCI1BUFR;
	//##########################################################
	if(!SSCI1DA)
	{// 地址匹配
		if(TempDate==0x00)
		{ // 这是一个广播地址的匹配，一定是写，否则也不会中断的非广播识别

			// 根据需要编写自己的广播应答代码,等后续时间
			//~~~~~~~ 做标志,或直接的处理代码

			// 比如，广播主要解决从机的错误逻辑，使能从机归位。
			SSCI1ADD=I2C_Add_H;  			// 仅样例，这里假定10bit模式，地址状态要还原
			Mode10bitsAddrUAState=0;		// 10bit地址状态机恢复
			SSCII2C_DateStruct.Command=0;	// 命令或地址默认回 0
			//
			SSCI1CKP=1; return;				// 使能主机i2c时钟 并退出中断应答。
		}
		else
		{ // 清零广播标志

		}
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		if(!SSCI1RW)
		{// 写的地址匹配的初始化   写的数据的第一个内容约定为地址或命令   后续的才是目标数据
			isSampleRevCommandAddr=1;
		}

		if(SSCI1UA)
		{// 10 位的    // 更新地址会硬件清零
				if(Mode10bitsAddrUAState)
				{// 标志下第二次的还原为下次启动的高地址匹配
					SSCI1ADD=I2C_Add_H;
					Mode10bitsAddrUAState=0;
					//
					if(SSCI1RW)
					{// 读时的第一个数据内容准备
						if(SSCII2C_DateStruct.Command<DATAI2C_BUFFER_BYTE_SIZE)  // 防溢出
							SSCI1BUFR=SSCII2C_DateStruct.buffer[SSCII2C_DateStruct.Command++];
					}
				}
				else
				{// 首次切换到低地址等待匹配   0
					SSCI1ADD=I2C_Add_L;
					Mode10bitsAddrUAState=1;
				}
		}
		else
		{ // 7位 的
			if(SSCI1RW)
			{// 读时的第一个数据内容准备
				if(SSCII2C_DateStruct.Command<DATAI2C_BUFFER_BYTE_SIZE)  // 防溢出
					SSCI1BUFR=SSCII2C_DateStruct.buffer[SSCII2C_DateStruct.Command++];
			}
		}
	}
	else
	{// 数据匹配
		if(!SSCI1RW)
		{ // 处于写的周期内容
			if(isSampleRevCommandAddr)
			{// 设计的第一个数据为地址 或命令号
				isSampleRevCommandAddr=0;
				SSCII2C_DateStruct.Command=TempDate;
			}else
			{// 后需的为数据，直接的写数据
				if(SSCII2C_DateStruct.Command<DATAI2C_BUFFER_BYTE_SIZE)  // 防溢出
					SSCII2C_DateStruct.buffer[SSCII2C_DateStruct.Command++]=TempDate;
			}
		}
		else
		{// 读基于写入的或保持的地址或命令号进行继续的数据返回
		 // 除非写再次修改SSCII2C_DateStruct.Command
			if(SSCII2C_DateStruct.Command<DATAI2C_BUFFER_BYTE_SIZE)  // 防溢出
				SSCI1BUFR=SSCII2C_DateStruct.buffer[SSCII2C_DateStruct.Command++];
		}
	}
	// 从机处理完毕，使能SCK时钟，可使主机继续进行命令的执行
		SSCI1CKP=1;
}

//***********************************************************************************
//主函数
//***********************************************************************************


void main()
{
	SYS_Init();

	//.....
	// 样例：7位或10位模式   1:10位    0:7位           bit0
	isSampleMode10bits=0;
	// 样例：主机或从机           1:  主    0:从           bit1
	isSampleModeMaster=1;

	// 主从模式配置
	if(isSampleModeMaster)
	{
		I2C_Configuration_Master();   // 样例中的 7位或10位模式样例根据标志来，否则直接冗余代码删除
		Delay_Wait_CLK_Sample(200);   // 主机应滞后从机就绪后启动i2c传输
	}
	else
		I2C_Configuration_Slave();	  // 样例中的 7位或10位模式样例根据标志来，否则直接冗余代码删除
//	while(1);
	// 样例观察引脚
	//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
	AIE=1;PUIE=1;
	while(1)
	{// 注：调试时第二次循环指向上一行代码，此为正常现象，是因为嵌汇编的XX指令在KF工具中没有行号，被属于需上一行代码的尾指令。
		_CWDT_();  // 周期性清理，如果代码执行时间超过看门狗的周期，应该在代码中插入清狗指令。
		//从机样例 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
		if(!isSampleModeMaster)
		{
			if(SSCI_I2C_State.bit0==1)
			{
				SSCI_I2C_State.bit0=0;

			}
		}
		//主机样例 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
		if(isSampleModeMaster)
		{
			//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			// 结构体化写操作	支持多从机，否则不用传递参数，方法中固定地址即可
			if(isSampleMode10bits)
			{// 10bit
				SSCII2C_DateStruct.i2cDevAddrL=I2C_Add_L;
				SSCII2C_DateStruct.i2cDevAddrH=I2C_Add_H|0x00; // 写的地址
			}
			else
			{// 7bit
				SSCII2C_DateStruct.i2cDevAddrL=I2C_Add_L|0x00; // 写的地址
			}
			SSCII2C_DateStruct.length=1;
			SSCII2C_DateStruct.Command=1;
			SSCII2C_DateStruct.buffer[0]=0x5A;
			I2C_Write_s_Data();
			// 结构体化读操作	支持多从机，否则不用传递参数，方法中固定地址即可
			if(isSampleMode10bits)
			{// 10bit
				SSCII2C_DateStruct.i2cDevAddrL=I2C_Add_L;
				SSCII2C_DateStruct.i2cDevAddrH=I2C_Add_H|0x00; // 仍然写的地址，除非配套方法的不需要标准EEprom协议的先写定位地址
			}
			else
			{// 7bit
				SSCII2C_DateStruct.i2cDevAddrL=I2C_Add_L|0x00;// 仍然写的地址，除非配套方法的不需要标准EEprom协议的先写定位地址
			}
			SSCII2C_DateStruct.length=2;
			SSCII2C_DateStruct.Command=0;

			I2C_Read_s_Data();

			if(SSCII2C_DateStruct.buffer[1]!=0x5A)
			{
				// 样例失败标记
				while(1);

			}
			//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

			// 通用化的读写操作               设备地址固定在函数实现中
			SSCII2C_DateStruct.bytes[0]=0x77; // 测试用例数据
			I2C_Write_Data(1,1,SSCII2C_DateStruct.bytes);
			I2C_Read_Data(0,2,SSCII2C_DateStruct.bytes);

			if(SSCII2C_DateStruct.bytes[1]!=0x77)
			{
				// 样例失败标记
				while(1);
			}
			//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		}
		//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
		Delay_Wait_CLK_Sample(200);
		Delay_Wait_CLK_Sample(200);
		Delay_Wait_CLK_Sample(200);
		Delay_Wait_CLK_Sample(200);
		Delay_Wait_CLK_Sample(200);
		Delay_Wait_CLK_Sample(200);
	}
}
//中断函数0:0X04入口地址
void int_fun0() __interrupt (0)
{
	// 从机
	if(SSCI1IE && SSCI1IF)
	{
		SSCI1IF=0;
		I2C_Slave_ISR_Server();// 如果需要 方法使用标记   如 SSCI_I2C_State.bit0=1;
	}
}


//中断函数1:0x14入口地址
void int_fun1() __interrupt (1)
{

}
