因为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