在使用I2C外设HAL库或者LL库例程作为从机时进行通信时,如果我们使用发送/接收库函数对从机进行初始化,就要按照官方例程这样,先写一个接收初始化函数,并设定接收字节长度,接收buffer,查询从机I2C状态后,再执行一次发送初始化函数。
如果我们非常清楚主机的发送时序,这样没有任何问题。但如果我们不知道主机何时开始发送,何时开始接收,也不知道主机会发送/接收多少个byte长度,这样的写法就会有问题。
/* I2C初始化 */
I2cHandle.Instance = I2C; /* I2C */
I2cHandle.Init.ClockSpeed = I2C_SPEEDCLOCK; /* I2C通讯速度 */
I2cHandle.Init.DutyCycle = I2C_DUTYCYCLE; /* I2C占空比 */
I2cHandle.Init.OwnAddress1 = I2C_ADDRESS; /* I2C地址 */
I2cHandle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; /* 禁止广播呼叫 */
I2cHandle.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; /* 允许时钟延长 */
if (HAL_I2C_Init(&I2cHandle) != HAL_OK)
{
APP_ErrorHandler();
}
/*I2C从机中断方式接收*/
while (HAL_I2C_Slave_Receive_IT(&I2cHandle, (uint8_t *)aRxBuffer, DARA_LENGTH) != HAL_OK)
{
APP_ErrorHandler();
}
/*判断当前I2C状态*/
while (HAL_I2C_GetState(&I2cHandle) != HAL_I2C_STATE_READY);
/*I2C从机中断方式发送*/
while (HAL_I2C_Slave_Transmit_IT(&I2cHandle, (uint8_t *)aTxBuffer, DARA_LENGTH) != HAL_OK)
{
APP_ErrorHandler();
}
/*判断当前I2C状态*/
while (HAL_I2C_GetState(&I2cHandle) != HAL_I2C_STATE_READY);
为了探究更灵活的处理方式,我们可以从硬件I2C的中断逻辑入手,根据官方手册:EV表示在I2C通信过程中,从机会产生的中断。
我们先定义从机I2C需要发送、接收的数据buffer,数据size等数据结构:
/*---------IIC1---------------*/
#define BUFFER_LENGTH 140
uint8_t aTxBuffer[BUFFER_LENGTH];//发送缓存
uint8_t aRxBuffer[BUFFER_LENGTH];//接收缓存
uint8_t TxIndex = 0;//发送计数
uint8_t RxIndex = 0;//接收计数
uint8_t FlagRcvOk = 0;// 接收完成标志
然后根据上图的I2C中断需要处理的节点,编写处理函数:
void User_I2C_EV_IRQHandler(void)
{
static uint32_t SR1Register =0;
static uint32_t SR2Register =0;
SR1Register = I2C1->SR1;
SR2Register = I2C1->SR2;
/* I2C1是从机(MSL = 0) */
/* 主机已发送地址,地址为被置位·(ADDR = 1: EV1(包括发送和接收)) */
if((SR1Register & 0x0002) == 0x0002)
{
RxIndex=0;
TxIndex=0;
/* 验证传输方向,Read 时的方向,Slave 进入接收器模式 */
if((uint32_t)(READ_BIT(SR2Register, I2C_SR2_TRA)) == 0x00000000U)//从机读
{
}
else //从机写
{
}
}
/* 接收数据(RXNE = 1: EV2) */
if((SR1Register & 0x0040) == 0x0040)
{
aRxBuffer[RxIndex++] = I2C1->DR;
}
/* 检测到停止条件(STOPF =1: EV4) */
if((SR1Register & 0x0010) == 0x0010)
{
I2C1->CR1 |= 0x0001;
FlagRcvOk = 1;
}
//无应答执行
if((I2C1->SR1 & I2C_SR1_AF)==I2C_SR1_AF)
{
CLEAR_BIT(I2C1->SR1,I2C_SR1_AF);
FlagRcvOk = 1;
}
/* 发送数据(TxE = 1: EV3、EV3-1) */
if((SR1Register & 0x0080) == 0x0080)
{
I2C1->DR = aTxBuffer[TxIndex++];
}
}
在中断函数里面调用处理函数,并做好发生错误中断时需要进行的处理:
void I2C1_IRQHandler(void)
{
User_I2C_EV_IRQHandler();
if((I2C1->SR1 & I2C_SR1_BERR)==I2C_SR1_BERR) //总线错误执行
{
CLEAR_BIT(I2C1->SR1,I2C_SR1_BERR);
I2C_Init();
}
if((I2C1->SR1 & I2C_SR1_OVR)==I2C_SR1_OVR) //过载执行
{
CLEAR_BIT(I2C1->SR1,I2C_SR1_OVR);
I2C_Init();
}
if((I2C1->SR1 & I2C_SR1_PECERR)==I2C_SR1_PECERR) //PEC执行
{
CLEAR_BIT(I2C1->SR1,I2C_SR1_PECERR);
I2C_Init();
}
}
贴一下HAL库的I2C配置函数,配置完开启中断就好了:
void I2C_Init(void)
{
I2cHandle.Instance = I2C; //I2C
I2cHandle.Init.ClockSpeed = I2C_SPEEDCLOCK; //I2C通讯速度
I2cHandle.Init.DutyCycle = I2C_DUTYCYCLE; //I2C占空比
I2cHandle.Init.OwnAddress1 = I2C_ADDRESS; //I2C地址
I2cHandle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; //禁止广播呼叫
I2cHandle.Init.NoStretchMode = I2C_NOSTRETCH_ENABLE; //允许时钟延长
if (HAL_I2C_DeInit(&I2cHandle) != HAL_OK) //I2C初始化
{
Error_Handler();
}
__HAL_RCC_I2C_CLK_DISABLE();
if (HAL_I2C_Init(&I2cHandle) != HAL_OK) //I2C初始化
{
Error_Handler();
}
SET_BIT(I2C1->CR1, I2C_CR1_ACK);//使能应答
SET_BIT(I2C1->CR2, I2C_IT_EVT);
SET_BIT(I2C1->CR2, I2C_IT_BUF);
SET_BIT(I2C1->CR2, I2C_IT_ERR);
}
最后在main函数里面更新从机要发送的数据,直接判断完成标志后,对buffer进行数据赋值就好了:
int main(void)
{
unsigned char i;
HAL_Init();
I2C_Init();//I2C初始化
for(i = 0;i < DARA_LENGTH;i++)
{
aTxBuffer[i]=i;
}
while(1)
{
if(FlagRcvOk)
{
FlagRcvOk = 0;
for(i = 0;i < DARA_LENGTH;i++)
{
aTxBuffer[i]++;
}
}
}
}
完成这些操作,这时候就可以让PY32作为从机进行I2C通信并自由应答了!
程序写完用逻辑分析器看一下波形,非常nice:
完结撒花✿✿ヽ(°▽°)ノ✿
>>>【点击进入,普冉PY32仿真烧录工具】https://zhuanlan.zhihu.com/p/673851365