126.n32使用gpio模拟iic从模式,全靠外部中断实现

因为n32的iic控制器用起来太烂了,达不到可靠稳定。

最后没得办法,手动给模拟实现以下,目前已经使用了一段时间,比控制器稳定得多。

使用了自定义的缓存,代码参考如下:

cpp 复制代码
/* The circle buffer function defined by warren */
#define CIRC_SIZE   64      /* serial frame circle buffer size 2023-0310 128 ---> 64*/
/* serial frame circle buffer initialize  */
#define CIRC_RELEASE(cb)        (cb.tail = cb.head = 0)
/* Return count in buffer.  */
#define CIRC_RM_CNT(cb)     ((CIRC_SIZE + cb.head - cb.tail) % CIRC_SIZE)
/* Put one word to circle buffer.  */
#define CIRC_PUT_CH(cb, val)    {cb.buf[cb.head] = val; cb.head = (cb.head + 1) % CIRC_SIZE;}
/* Get one word from circle buffer.  */
#define CIRC_GET_CH(cb, val)    {val = cb.buf[cb.tail]; cb.tail = (cb.tail + 1) % CIRC_SIZE;}

n32g45x_i2c1_slave_gpio.c文件

cpp 复制代码
#include "n32g45x_i2c1_slave_gpio.h"
#include "stdio.h"
#include "main.h"

#define I2C1_SCL_PIN GPIO_PIN_8
#define I2C1_SDA_PIN GPIO_PIN_9
#define SDA_GPIO        GPIOB
#define SCL_GPIO        GPIOB


#define IIC1_SLAVE_ADDR 0x40  //7bit addr 不包括读写位

volatile uint8_t g_iic0_send_size = 7;  //默认是发送7个字节
volatile uint8_t g_iic1_enterint_timeout = 2;  //iic 状态机是否超时,2表示不判断超时,0表示刚刚进入了中断
//volatile uint8_t g_iic0_is_Idle = 1;  //0表示非空闲,1表示空闲

typedef enum{
    IDLE = 1,
    ADDR = 2,
	ADDR_MATCH,
    RECEIVING,
    SENDING,
	ACK_ADDR_MATCH,     //匹配地址需要应答
	ACK_TO_RECEIV,
	ACK_TO_SEND,
	WAIT_ACK_END_TO_RECEIV,   //等待发送的ack周期结束
	WAIT_ACK_END_TO_SEND,
	READ_MASTER_ACK,      //读主机的应答,来判断是否结束了
	WAIT_STOP,   //不是自己的数据,等待本次iic过程结束
	SEND_COMPLETE
}Slave_State;
Slave_State state;



static void IIC_W_SDA(uint8_t value)   
{
	GPIO_WriteBit(SDA_GPIO,I2C1_SDA_PIN,value?Bit_SET:Bit_RESET);   
}

#if 0
static uint8_t IIC_R_SDA()
{
   return GPIO_ReadInputDataBit(SDA_GPIO,I2C1_SDA_PIN);  
}

static void IIC_W_SCL(uint8_t value)
{
  GPIO_WriteBit(SCL_GPIO,I2C1_SCL_PIN,value?Bit_SET:Bit_RESET);   
}

static uint8_t IIC_R_SCL()
{ 
   return GPIO_ReadInputDataBit(SCL_GPIO,I2C1_SCL_PIN);     
}
#endif 


//输出时,SDA中断应该关闭
static void SDA_int_init(FunctionalState stat){
    EXTI_InitType EXTI_InitStruct9;
	//SDA
    GPIO_ConfigEXTILine(GPIOB_PORT_SOURCE,GPIO_PIN_SOURCE9); //IO口与中断线的映射(选择)      
    EXTI_InitStruct9.EXTI_Line=EXTI_LINE9; //配置中断触发模式
    EXTI_InitStruct9.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStruct9.EXTI_Trigger=EXTI_Trigger_Rising_Falling;
    EXTI_InitStruct9.EXTI_LineCmd = stat;
    EXTI_InitPeripheral(&EXTI_InitStruct9);
    EXTI_ClrITPendBit(EXTI_LINE9);//清除初始化造成的标志位
}





//配置SDA为输出
static  void SDA_Gpio_Init_Out(void)
 {
    GPIO_InitType GPIO_InitStructure;
	 
	// SDA_int_init(DISABLE);  //输出模式不要中断了
	 
    GPIO_InitStructure.Pin = I2C1_SDA_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;  // 开漏输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitPeripheral(SDA_GPIO, &GPIO_InitStructure); 
 }
 
#if 1 
//配置SDA为输入
static void SDA_Gpio_Init_In(void)
{
	GPIO_InitType GPIO_InitStructure;
	GPIO_InitStructure.Pin = I2C1_SDA_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitPeripheral(SDA_GPIO, &GPIO_InitStructure);
	
	SDA_int_init(ENABLE);  //输入模式要中断
}
#endif

static void SCL_Gpio_Init_In(void)
{
	GPIO_InitType GPIO_InitStructure;
	GPIO_InitStructure.Pin = I2C1_SCL_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitPeripheral(SDA_GPIO, &GPIO_InitStructure);
}



static void SCL_int_init(FunctionalState stat)
{
	EXTI_InitType EXTI_InitStruct8;
    //SCL    
    GPIO_ConfigEXTILine(GPIOB_PORT_SOURCE,GPIO_PIN_SOURCE8); //IO口与中断线的映射(选择)    
    EXTI_InitStruct8.EXTI_Line=EXTI_LINE8; //配置中断触发模式
    EXTI_InitStruct8.EXTI_Mode=EXTI_Mode_Interrupt;
    EXTI_InitStruct8.EXTI_Trigger=EXTI_Trigger_Rising_Falling;  //双边沿
    EXTI_InitStruct8.EXTI_LineCmd=stat;    
    EXTI_InitPeripheral(&EXTI_InitStruct8);//EXTI_Init(&EXTI_InitStruct8);
    EXTI_ClrITPendBit(EXTI_LINE8);//清除初始化造成的标志位
}






static void NVIC_Configuration_IIC1_Gpio(FunctionalState stat)
{
    NVIC_InitType NVIC_InitStructure;

 
    NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
    
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd                = stat;  //ENABLE or DISABLE
    NVIC_Init(&NVIC_InitStructure);

}




void i2c1_slave_gpiomode_init(void)
{
	RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOB, ENABLE);   //gpio 时钟使能
	
	SDA_Gpio_Init_In();  //引脚初始化为in
	SCL_Gpio_Init_In();  //引脚初始化为in
	
	
	SCL_int_init(DISABLE); //外部中断使能,先不使能SCL
	SDA_int_init(ENABLE); //外部中断使能
	
	NVIC_Configuration_IIC1_Gpio(ENABLE);  //nvic 开启中断使能
	
}


void i2c1_slave_init(void)
{
	i2c1_slave_gpiomode_init();
}




void EXTI9_5_IRQHandler(void)
{   
	static uint8_t last_sda = 0;
	static uint8_t rev_bit_count = 0;
	static uint8_t send_bit_count = 0;	
	static uint8_t Rx_byte;//,Tx_byte;
	static uint8_t tdata;
	uint8_t tdata_count;
	uint8_t sda,scl;
	
	//gpio8 是SCL,双边沿中断
    if(EXTI_GetITStatus(EXTI_LINE8) != RESET){//数据接收
        EXTI_ClrITPendBit(EXTI_LINE8);
		
		g_iic1_enterint_timeout = 0;
		if(state == WAIT_STOP){  //地址不匹配时,等待本次总线的iic过程结束
			//printf("WAIT_STOP\r\n");
			return;
		}
		
		if(GPIO_ReadInputDataBit(GPIOB, I2C1_SCL_PIN))  //SCL高电平
		{
			//SCL拉高,这个阶段是主机读取ack的时序,单片机不处理,
			if(state == WAIT_ACK_END_TO_RECEIV || state == WAIT_ACK_END_TO_SEND){
				//printf("W_A\r\n");
				return;
			}
			
			if(READ_MASTER_ACK == state)  //单片机发送数据后,需要判断主机是否还要继续读
			{
				sda = GPIO_ReadInputDataBit(GPIOB, I2C1_SDA_PIN);
				if(sda)
					state = WAIT_STOP;   //没有应答,读取流程结束
				else{
					state = SENDING;    //还要继续发送数据
					SDA_Gpio_Init_Out();// SDA输出模式
				}				
				return;
			}
			
			
			if(state == ADDR || state == RECEIVING){ //state状态机
				//printf("*");
			   //此处进行数据1bit的接收
				sda = GPIO_ReadInputDataBit(GPIOB, I2C1_SDA_PIN);
				last_sda = sda;  //保存sda的最后状态

				Rx_byte <<= 1;
				Rx_byte |= sda;
				rev_bit_count++;
				if(rev_bit_count == 8){   //接收完成1个字节数据
					rev_bit_count = 0;  //清零计数
				  if(state == ADDR){
					 if(Rx_byte>>1 == IIC1_SLAVE_ADDR)  // 只比较7bits
					 {		
						 //printf("ADDR match\r\n");		
						 if(Rx_byte & 1)  //1 主机读操作,单片机发送数据
						 {
							 state = ACK_TO_SEND;   //单片机发送数据
							 send_bit_count = 0;
	
						 }
						 else //0 主机写操作,单片机是接收数据
						 {
							 state = ACK_TO_RECEIV;  //单片机接收数据
							 //printf("master write\r\n");	
						 }
					 }
					 else //地址不匹配 
					 {
						 //printf("ADDR unmatch,wait stop\r\n");
						 state = WAIT_STOP;
						 //scl的中断可以关了
						 SCL_int_init(DISABLE);  //关闭中断了
					 }
					  
				  }else if(state == RECEIVING){ //存储数据
					  //buffer[i++] = Rx_byte;
					  //printf("pp\r\n");
					  CIRC_PUT_CH(g_i2c0_rxbuf, Rx_byte);  //收到数据
					  
					  state = ACK_TO_RECEIV;  //收到数据也要应答
				  }     
				}
			}
			else if(state == SENDING) //发送数据阶段
			{
				//nothing todo
			}
		}
		else//这个else scl是低电平
		{			
			if(state == ACK_TO_SEND || state == ACK_TO_RECEIV) //接收数据时需要发送ack
			{
				SDA_Gpio_Init_Out();//IO口切换为输出
				IIC_W_SDA(0);//Ackbit:0 拉低SDA,准备ACK  1:非应答
								
				if(state == ACK_TO_SEND)
					state = WAIT_ACK_END_TO_SEND; 
				else
					state = WAIT_ACK_END_TO_RECEIV;  //还需要等待ACK的周期结束
								
			}
			else if(state == WAIT_ACK_END_TO_RECEIV){
				//printf("xx\r\n");
					state = RECEIVING;
					SDA_Gpio_Init_In(); //释放SDA,回到输入模式(上拉输入)
			}
			else if(state == WAIT_ACK_END_TO_SEND)   //原来是输出状态,就不切换了
			{
				state = SENDING;
			}
			
			//发送数据到总线
			if(state == SENDING) //发送数据阶段
			{				
				if(send_bit_count == 0)  //第一次要发送数据,就要读一个字节
				{
					tdata_count = CIRC_RM_CNT(g_i2c0_txbuf);
					if( tdata_count > 0)   //发送缓存有数据 && (i%2)
					{
						CIRC_GET_CH(g_i2c0_txbuf, tdata);   //取出数据,
					}
					else
						tdata = 0;   //没有数据时,填充0
				}
								
				
				//SDA写入数据,高位在前,低位在后
				if(send_bit_count < 8)
					IIC_W_SDA((tdata & (0x80 >> send_bit_count)));
				else if(send_bit_count == 8)
				{
					//IIC_W_SDA(0);  //第9bit 其实应该是单片机读主机的ack bit,这里就直接不读了,不关心
					SDA_Gpio_Init_In();//IIC_W_SDA(0);  //第9bit 其实应该是单片机读主机的ack bit,这里就直接不读了,不关心
					state = READ_MASTER_ACK;   //下一次读取 主机应答
				}
				send_bit_count++;
				if(send_bit_count == 9){
					send_bit_count = 0;
				}				
			}
		}
    }
	
	  //起始停止信号的判断  gpio9 是 SDA 
    if(EXTI_GetITStatus(EXTI_LINE9) != RESET) {   //判断起始及停止信号
		EXTI_ClrITPendBit(EXTI_LINE9); 
		g_iic1_enterint_timeout = 0;  //进入中断了
        scl = GPIO_ReadInputDataBit(GPIOB, I2C1_SCL_PIN);
		
		if(scl == 0){ //不关心
			last_sda = GPIO_ReadInputDataBit(GPIOB, I2C1_SDA_PIN); 
			return;
		}
        sda = GPIO_ReadInputDataBit(GPIOB, I2C1_SDA_PIN);        
                             
        // START 条件:SDA 下降沿,且 SCL 为高
        if(last_sda == 1 && sda == 0 && scl == 1){
            state = ADDR;    //下一步准备接收数据
            rev_bit_count = 0;
            Rx_byte = 0;
			SCL_int_init(ENABLE);  //开启SCL中断了,2025-12-04
        }else if(last_sda == 0 && sda == 1 && scl == 1) { //STOP
            state = IDLE;
            rev_bit_count = 0;
            Rx_byte = 0;	
			i2c1_slave_gpiomode_init();   //设置gpio到输入模式,开启中断 			
			g_iic1_enterint_timeout = 2;	//不需要做超时处理了。	
			//SCL_int_init(DISABLE);  //关闭SCL中断了	,2025-12-04		
        }
        last_sda = sda;       
    }

	
}

//每100ms调用一次,检查是否iic时序错乱超时了
void check_iic_enter_timeout(void)
{
	if(g_iic1_enterint_timeout == 0)
		g_iic1_enterint_timeout = 1;
	else if(g_iic1_enterint_timeout == 1)  //经过100ms 检测是1,就说明很久没有中断了
	{
		printf("check_iic_enter_timeout timeout == 1\r\n");
		if(state != IDLE)
		{			
			printf(" i2c1_slave_gpiomode_init state = %d\r\n",state);
			i2c1_slave_gpiomode_init();   //设置到输入模式,开启中断
			state = IDLE;   //切换到空闲模式
		}
//		g_iic0_is_Idle = 1; 
		g_iic1_enterint_timeout = 2;
	}
}

n32g45x_i2c1_slave_gpio.h文件没啥内容

cpp 复制代码
#ifndef __IIC_DRIVE_SLAVE_H__
#define __IIC_DRIVE_SLAVE_H__


#include "n32g45x.h"


#endif
相关推荐
集芯微电科技有限公司1 小时前
40V/3A高性能高集成三相BLDC驱动器具有电流及故障诊断功能(FLT)
c语言·数据结构·单片机·嵌入式硬件·fpga开发
li星野2 小时前
打工人日报#20251204
单片机·嵌入式硬件
EVERSPIN2 小时前
国产32位MCU语音识别方案
单片机·嵌入式硬件·语音识别·32位mcu
d111111111d3 小时前
在stm32F103C8T6中,Thumb指令是什么?有什么作用?可以干什么?
笔记·stm32·单片机·嵌入式硬件·学习
MARIN_shen3 小时前
Marin说PCB之LPDDR5的仿真报告分析--02
嵌入式硬件·硬件工程·信号处理·pcb工艺
DIY机器人工房3 小时前
简单理解:ESP32S3 开发板中通过什么控制舵机
stm32·单片机·嵌入式硬件·mcp·小智ai·diy机器人工房
victorwjw3 小时前
STM32 ISP下载原理
stm32·嵌入式硬件
沐欣工作室_lvyiyi3 小时前
基于单片机的智能宠物监护设备(论文+源码)
stm32·单片机·毕业设计·宠物监护
cjy_Somnr3 小时前
电机驱动学习以及闭环控制和硬件连接
嵌入式硬件·学习