软件模拟IIC从机

由于ic性能有限,没有硬件的iic外设,项目上有需要使用iic从机,所以自己写了个iic从机模块,执行效率还行可以10m主频的单片机最快可以接收到100K的速度。该模块需要资源,2个外部中断配置-上升下降沿中断,来模拟iic时序,芯片编译器的问题,对结构体不是很友好,所以这边没有使用结构体对变量打包,目前只实现了简单的接受和发送,不支持eeprom那种读写方式。附带工程源码下载路径,0积分

在这里插入图片描述
两组io口配置,总共有4种状态:
1 SDA下降沿中断
SCL高电平 – 起始信号
SCL低电平 – 数据0 或 ACK
2 SDA上升沿中断
SCL高电平 – 停止信号
SCL低电平 – 数据1 或NACK
3 SCL 下降沿
数据交换 主机从机 写电平
4 SCL 上升沿
数据处理 主机 从机 读电平
为了更好模拟硬件,当iic空闲状态时,监测start信号,当监测到start信号时才打开SCL中断开启接受,iic接受完成或者异常时关闭,参照标准iic协议,按电平变化处理整个iic时序
实现前准备

typedef enum{
	IIC_NO_OPERAT = 0,
	IIC_WRITE_START,
	IIC_WRITE,
	IIC_WRITE_FINSH,
    IIC_READ_START,
	IIC_READ,
	IIC_READ_FINSH,
	IIC_ADDR_NO_MATCH,
	IIC_OVERFLOW,
	IIC_ERROR,
}IIC_STATUS_U;
		
#define BUFF_SIZE 15
extern u8 g_iic_status;
extern u8 g_iic_buff[BUFF_SIZE];
extern u8 g_iic_len;

void iic_slaver_init(void);
void iic_sda_proess(void);
void iic_scl_proess(void);
/``````````````````````````````````````````````c文件`````````````````````````````````````````/
typedef enum{
	STEP_JETECT_ADDR = 0,
	STEP_DETECT_WRITE_OR_READ,
	STEP_SEND_DATA,
	STEP_READ_ACK,
	STEP_STOP,
	STEP_RESTART,
	STEP_READ_DATA,
	STEP_SEND_ACK,
}IIC_STEP_U;
enum{
	ACK = 0,
	NACK
};
/* 本机地址 */
#define IIC_LOCAL_ADDRESS 0x78

#define iic_sda_set_out()    IOSTA &= ~Port_A3
#define iic_sda_set_in()     IOSTA |= Port_A3 
#define iic_sda_read()       PORTAbits.PA3  
#define iic_sda_send_high()  PORTAbits.PA3 = 1
#define iic_sda_send_low()   PORTAbits.PA3 = 0
#define iic_scl_read()       PORTAbits.PA4
/* scl中断开关前都需要清除标志位 */
#define scl_interrupt_on() INTFbits.INT0IF = 0;INTE |= BIT(2)
#define scl_interrupt_off() INTFbits.INT0IF = 0;INTE &= ~BIT(2)
#define sda_interrupt_flag_clr() INTFbits.INT1IF = 0
/* iic当前的状态,没有使用队列,如果主函数里面有长延时注意可能会被覆盖 */
u8 g_iic_status = IIC_NO_OPERAT;
u8 g_iic_len;	/* 一次iic接收数据的长度 */
u8 g_iic_buff[BUFF_SIZE];
static u8 s_iic_temp = 0; /* 一次iic接收数据的长度 */
static u8 s_iic_num = 0; /* 处理数据对于的数组下标 */
static u8 s_iic_step = STEP_STOP;/* iic处理的程序步 */
static u8 s_iic_bit_cnt = 0;/* 一个字节处理时对于的bit号 */

实现过程 sda处理过程

/* 
	sda IO口中断
	sda发生电平变化时,scl是高电平,则是start或者stop信号,scl是低电平,则是数据变化信号 
	当前不支持发送完地址之后发送指定地址数据再读,只支持发送完地址之后立即读
*/
void iic_sda_proess(void)
{
	/* 数据变化信号 */
	if(0 == iic_scl_read()){
		return;
	}
	/* start 信号 */
	if(0 == iic_sda_read()){ 
		s_iic_step = STEP_JETECT_ADDR;
		scl_interrupt_on();
	} else if(1 == iic_sda_read()){ 
		/* 
		stop 信号 逻辑分析仪抓到,应答后scl先拉高,再等sda拉高,
		这个过程会触发一次scl中断,系统会开始下一次轮接收,
		停止信号确认之后这里会把所有状态清除,所以可以忽略
		*/
		if (IIC_WRITE == g_iic_status){
			g_iic_status = IIC_WRITE_FINSH;
			g_iic_len = s_iic_num;
		}else if (IIC_READ == g_iic_status){
			g_iic_status = IIC_READ_FINSH;
		}else if (IIC_ERROR == g_iic_status){
			
		}
		scl_interrupt_off();
		s_iic_step = STEP_STOP;
	} 
	s_iic_bit_cnt = 0;
	s_iic_temp = 0;
	s_iic_num = 0;
}

scl下降沿处理过程

/*
	scl 下降沿处理过程
*/
void iic_scl_falling_edge_proess(void)
{
	switch (s_iic_step){
	/* 校验丛机地址,等主机动作,从机等上升沿读电平 */	
	case STEP_JETECT_ADDR:

		break;
	/* 读主机的读写位,等主机动作,从机等上升沿读电平 */	
	case STEP_DETECT_WRITE_OR_READ:

		break;
	/* 主机写数据,从机等上升沿读电平,先将io设为输入模式 */
	case STEP_READ_DATA:
		iic_sda_set_in();
		break;
	/* 主机读数据,从机发送1bit数据,先发高位,主机等上升沿读电平 */
	case STEP_SEND_DATA:
		iic_sda_set_out();
		if (s_iic_temp&(0x80 >> s_iic_bit_cnt)){
			iic_sda_send_high();
		}else{
			iic_sda_send_low();
		}
		break;
	/* 从机发送应答信号,主机等上升沿读电平 */
	case STEP_SEND_ACK:
		/* sda的输入输出模式发生改变,清除中断标志位防止发生误触中断 */
		sda_interrupt_flag_clr();
		iic_sda_set_out();
		iic_sda_send_low(); //应答
		break;
	/* 读主机ack,等主机动作,从机io设置为输入等上升沿读电平 */	
	case STEP_READ_ACK:
		iic_sda_set_in();
		break;
	default:
		break;
	}
}

iic上升沿处理过程

/*
	scl 上升沿处理过程
	主机发送完一帧数据应答后,准备下一帧数据时候,scl的PB5电平会翻转两次,但接受好笑没有出错,暂时不清楚为什么会这样
*/
void iic_scl_rising_edge_proess(void)
{
	switch (s_iic_step){
	/* 校验丛机地址,读一个bit */	
	case STEP_JETECT_ADDR:
		s_iic_temp <<= 1;
		s_iic_temp += iic_sda_read();
		if (7 <= (++s_iic_bit_cnt)){
			if (IIC_LOCAL_ADDRESS == s_iic_temp){
				s_iic_step = STEP_DETECT_WRITE_OR_READ;
			}else{
				/* 地址不匹配,关闭scl中断,重新监测起始信号 */
				s_iic_step = STEP_STOP;
				g_iic_status = IIC_ADDR_NO_MATCH;
				scl_interrupt_off();
			}
			s_iic_bit_cnt = 0;
			s_iic_temp = 0;
		}
		break;
	/* 校验丛机地址成功,读主机的读写位 */
	case STEP_DETECT_WRITE_OR_READ:
		/*
		主机要读数据,从机发数据,将数据传入temp,所以要读的数据还提前需要填入buff,
		如果需要支持读不通的地址的数据的话还需要升级版本,定制协议 
		*/
		s_iic_step = STEP_SEND_ACK;
		if (1 == iic_sda_read()){ 
			g_iic_status = IIC_READ_START;
		}else if (0 == iic_sda_read()){ //主机要发数据,从机读数据			
			g_iic_status = IIC_WRITE_START;
			s_iic_num = 0;
		}
		break;
	/* 接收数据,读一个bit,先接高位 */
	case STEP_READ_DATA:
		s_iic_temp <<= 1;
		if (1 == iic_sda_read()){
			s_iic_temp |= 0x01;
		}
		if (8 <= (++s_iic_bit_cnt)){
			s_iic_bit_cnt = 0;
			s_iic_step = STEP_SEND_ACK;
		}
		break;
	/* 发送完一个bit,判断1个byte是否发送完,发送完成下一步读主机ack */
	case STEP_SEND_DATA:
		if (8 <= ++s_iic_bit_cnt){
			s_iic_bit_cnt = 0;
			s_iic_step = STEP_READ_ACK;
		}
		break;
	/* 发送完ack,准备下一byte接收 */
	case STEP_SEND_ACK:
		if (IIC_WRITE == g_iic_status){
			/* 将接收到的数据传入buff,然后number++ */
			g_iic_buff[s_iic_num++] = s_iic_temp;
			s_iic_temp = 0;
			s_iic_bit_cnt = 0;
			/* 溢出,不能再接受数据 */
			if (s_iic_num >= BUFF_SIZE){
				scl_interrupt_off();
				iic_sda_set_in();
				s_iic_num = 0;
				s_iic_step = STEP_STOP;
				g_iic_status = IIC_ERROR;
			}else{
				s_iic_step = STEP_READ_DATA;
			}	
		}else if (IIC_WRITE_START == g_iic_status){
			g_iic_status = IIC_WRITE;
			s_iic_step = STEP_READ_DATA;
			s_iic_bit_cnt = 0;
		}else if (IIC_READ_START == g_iic_status){
			s_iic_temp = g_iic_buff[0];/* 不支持从指定地址读出 */
			g_iic_status = IIC_READ;
			s_iic_step = STEP_SEND_DATA;
		} else {
			iic_sda_set_in();
		}
		/* sda的输入输出模式发生改变,清除中断标志位防止发生误触中断 */
		sda_interrupt_flag_clr();
		break;
	/* 读主机ack */	
	case STEP_READ_ACK:
		if (1 == iic_sda_read()){ /* 主机不应答,发生错误或一次iic传输结束了 */
			scl_interrupt_off();
			s_iic_num  = 0;
		}else if (0 == iic_sda_read()){ //主机应答
			s_iic_num ++;
			if (s_iic_num >= g_iic_len){ //溢出
				scl_interrupt_off();
				s_iic_num = 0;
			}else {
				/* 准备下一帧数据发送 */
				s_iic_temp = g_iic_buff[s_iic_num];
				s_iic_step = STEP_SEND_DATA;	
			}	
		}
		/* sda的输入输出模式发生改变,清除中断标志位防止发生误触中断 */
		sda_interrupt_flag_clr();
		break;
	default:
		break;
	}
}

猜你喜欢

转载自blog.csdn.net/lala0903/article/details/107363062