STM32作为I2C从机中断接收和从模式中断应答数据总结

之前在项目中用到了STM32F103的i2c功能,大致功能是两个单片机进行i2c通信,而且通信模式是主问从答模式。这里我研究了一下STM32F103硬件i2c作为从机中断接收主设备请求,然后从设备在主设备发送读信号时中断发送回应的功能。

在网上找了很多资料,都说STM32F103的i2c硬件有瑕疵,具体有啥瑕疵我也不太清楚,只是大部分例程都是用IO模拟I2C。我这里使用的是硬件i2c,由于手上有两块单片机:一个是STM32F103VET6,另一个是STM32L151C8T6。开始还是比较头疼的,因为两个芯片不一样,工程就差别很大,后面我找以前的工程给这两个片子做了一套工程,比较复杂,STM32F103的还上了FreeRTOS,STM32L151的就是用的在cubeMAX上配置的工程,后面还找了个单片机的shell例程移植到这两款单片机上,实现shell测试。

1,STM32F103从中断接收和应答


#include "ipmi.h"
#include "cyclebuffer.h"
#include "FreeRTOS.h"
#include "task.h"
#include "fifo.h"
#include "i2c_gpio.h"
#include <stdio.h>
#include <string.h>

#define I2C_SLAVE_ADDRESS7 	0x30
#define ClockSpeed 			50000

__IO uint8_t ipmi_RX_buf[256]={0};
tFifo ipmi_fifo;


uint8_t I2C2_Buffer_Tx[64]={0};
uint8_t Tx_Idx,Rx_Idx,rx_addr_match=0,slave_receive_data=0;
uint8_t i2cResponse[16]="hello world! 5";
/*Exported types ------------------------------------------------------------*/

EventStatus i2c_event= NOEVENT;

void ipmi_i2c_configure(void)
{
	I2C_InitTypeDef I2C_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;

	//I2C2 gpio congfig
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; //SCL SDA
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	/* Enable I2C2 clock */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
	/* Enable GPIOB clock */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	/* Configure and enable I2Cx event interrupt -------------------------------*/
	NVIC_InitStructure.NVIC_IRQChannel = I2C2_EV_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	/* Configure and enable I2C1 error interrupt -------------------------------*/
	NVIC_InitStructure.NVIC_IRQChannel = I2C2_ER_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
	NVIC_Init(&NVIC_InitStructure);

	/* I2C1 configuration ------------------------------------------------------*/
	I2C_DeInit(I2C2);
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;//模式
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
	I2C_InitStructure.I2C_OwnAddress1 = I2C_SLAVE_ADDRESS7;//作为从机的地址
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//
	I2C_InitStructure.I2C_ClockSpeed = ClockSpeed;
	I2C_Init(I2C2, &I2C_InitStructure);	

	/* Enable I2C1 event and buffer interrupts */
	I2C_ITConfig(I2C2, I2C_IT_EVT | I2C_IT_BUF, ENABLE);
	/* Enable I2C1 Error interrupts */
	I2C_ITConfig(I2C2, I2C_IT_ERR, ENABLE);
}


void ipmi_rx_fifo_init(void)
{
	FifoInit(&ipmi_fifo,(uint8_t *)ipmi_RX_buf,sizeof(ipmi_RX_buf));
	FifoFlush(&ipmi_fifo);
}

//时间终端处理函数
void I2C2_EV_IRQHandler(void)     
{ 
	uint8_t ch = 0,tempcnt=0;
	//获取中断事件
	switch (I2C_GetLastEvent(I2C2))
	{ 
		/* Slave Transmitter ---------------------------------------------------*/ 
		case I2C_EVENT_SLAVE_BYTE_TRANSMITTED: 
			/* 这个和下面那个都是从发生模式下发送数据的 */ 
			I2C_SendData(I2C2, i2cResponse[Tx_Idx]);
			Tx_Idx = 0;
			break; 

		case I2C_EVENT_SLAVE_BYTE_TRANSMITTING:             /* EV3 */  
			/* Transmit I2C1 data */
			I2C_SendData(I2C2, i2cResponse[Tx_Idx++]);
			break; 
		
		/* Slave Receiver ------------------------------------------------------*/ 
		case I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED:     /* EV1 */ 
			rx_addr_match = 1;
			break; 

		case I2C_EVENT_SLAVE_BYTE_RECEIVED:                /* EV2 */ 
			/* Store I2C2 received data */ 
			slave_receive_data= 1;
			ch = I2C_ReceiveData(I2C2);
			if(false==IsFifoFull(&ipmi_fifo))
			{
				FifoPush(&ipmi_fifo,ch);
			}
			break; 

		case I2C_EVENT_SLAVE_STOP_DETECTED:                /* EV4 */
			/* Clear I2C2 STOPF flag */ 
			I2C_Cmd(I2C2, ENABLE); 
			Rx_Idx=0; 
			i2c_event = EVENT_OPCOD_NOTYET_READ; 
			break; 

		default: 
			break;    

	} 

} 
 
void I2C2_ER_IRQHandler(void) 
{ 
	/* Check on I2C1 AF flag and clear it */ 
	if (I2C_GetITStatus(I2C2, I2C_IT_AF)) 
	{ 
		I2C_ClearITPendingBit(I2C2, I2C_IT_AF); 
		Tx_Idx = 0; 

		i2c_event = EVENT_OPCOD_NOTYET_READ; 
	} 

	/* Check on I2C1 AF flag and clear it */ 

	if (I2C_GetITStatus(I2C2, I2C_IT_BERR))  
	{ 
		I2C_ClearITPendingBit(I2C2, I2C_IT_BERR);
	}

}

/*
*********************************************************************************************************
*	函 数 名: ipmi_WriteBytes
*	功能说明: 向串行EEPROM指定地址写入若干数据,采用页写操作提高写入效率
*	形    参:_usAddress : 起始地址
*			 _usSize : 数据长度,单位为字节
*			 _pWriteBuf : 存放读到的数据的缓冲区指针
*	返 回 值: 0 表示失败,1表示成功
*********************************************************************************************************
*/
uint8_t ipmi_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint8_t ipmi_slave_addr, uint16_t _usSize)
{
	uint16_t i,m;
	uint16_t usAddr;
	
	/* 
		写串行EEPROM不像读操作可以连续读取很多字节,每次写操作只能在同一个page。
		对于24xx02,page size = 8
		简单的处理方法为:按字节写操作模式,没写1个字节,都发送地址
		为了提高连续写的效率: 本函数采用page wirte操作。
	*/

	usAddr = _usAddress;	
	for (i = 0; i < _usSize; i++)
	{
		/* 当发送第1个字节或是页面首地址时,需要重新发起启动信号和地址 */
		if ((i == 0) || (usAddr & (8 - 1)) == 0)
		{
			/* 第0步:发停止信号,启动内部写操作 */
			i2c_Stop();
			
			/* 通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms 			
				CLK频率为200KHz时,查询次数为30次左右
			*/
			for (m = 0; m < 100; m++)
			{				
				/* 第1步:发起I2C总线启动信号 */
				i2c_Start();
				
				/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
				i2c_SendByte(ipmi_slave_addr | I2C_WR);	/* 此处是写指令 */
				
				/* 第3步:发送一个时钟,判断器件是否正确应答 */
				if (i2c_WaitAck() == 0)
				{
					break;
				}
			}
			if (m  == 1000)
			{
				goto cmd_fail;	/* EEPROM器件写超时 */
			}
		
			/* 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 */
			i2c_SendByte((uint8_t)usAddr);
			
			/* 第5步:发送ACK */
			if (i2c_WaitAck() != 0)
			{
				goto cmd_fail;	/* EEPROM器件无应答 */
			}
		}
	
		/* 第6步:开始写入数据 */
		i2c_SendByte(_pWriteBuf[i]);
	
		/* 第7步:发送ACK */
		if (i2c_WaitAck() != 0)
		{
			goto cmd_fail;	/* EEPROM器件无应答 */
		}

		usAddr++;	/* 地址增1 */		
	}
	
	/* 命令执行成功,发送I2C总线停止信号 */
	i2c_Stop();
	return 1;

cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
	/* 发送I2C总线停止信号 */
	i2c_Stop();
	return 0;
}


unsigned short IpmbCmdMatchTimeout(unsigned char *str,unsigned short TimeOut)
{
	unsigned short cnt=0;
	unsigned char flag=0,temp,strbuf[96];
	char *ptr=NULL;

	memset(strbuf,0,sizeof(strbuf));

	while((TimeOut!=0))
	{
		while(!IsFifoEmpty(&ipmi_fifo))
		{
			TimeOut = 10;
			temp = FifoPop(&ipmi_fifo);
			strbuf[cnt++] = temp;
			printf("0x%02x ",temp);
			ptr=strstr((char *)&strbuf[1],(char *)str);
			if(ptr)
			{
				flag = 1;
				continue ;
			}
		}
		TimeOut--;
		vTaskDelay(10);
	}
	
	if(flag)
	{
		printf("找到了%s\r\n",str);
		return cnt;
	}
	printf("没找到%s\r\n",str);
	return 0;
}



从中断接收和处理,主要在与I2C2_EV_IRQHandler和I2C2_ER_IRQHandler这两个中断函数,之前一直不知道的是,STM32F103的I2C从中断模式如何返回数据给主机,知道大神提点后,知道了,在主设备问从设备的时候(主设备发送读信号)从设备可以在中断处理中发送数据给主机,但是这种模式依然是从设备,回复数据是被动的,必须主设备发送读信号。

2,STM32L151做主设备发送i2c数据帧和请求数据应答

由于STM32L151使用的是cubeMAX建的工程,使用的是hal库,代码就规范很多,也直接调用hal的I2C库函数进行i2c数据发送和读取:

/**
  ******************************************************************************
  * File Name          : I2C.c
  * Description        : This file provides code for the configuration
  *                      of the I2C instances.
  ******************************************************************************
  ** This notice applies to any and all portions of this file
  * that are not between comment pairs USER CODE BEGIN and
  * USER CODE END. Other portions of this file, whether 
  * inserted by the user or by software development tools
  * are owned by their respective copyright owners.
  *
  * COPYRIGHT(c) 2019 STMicroelectronics
  *
  * Redistribution and use in source and binary forms, with or without modification,
  * are permitted provided that the following conditions are met:
  *   1. Redistributions of source code must retain the above copyright notice,
  *      this list of conditions and the following disclaimer.
  *   2. Redistributions in binary form must reproduce the above copyright notice,
  *      this list of conditions and the following disclaimer in the documentation
  *      and/or other materials provided with the distribution.
  *   3. Neither the name of STMicroelectronics nor the names of its contributors
  *      may be used to endorse or promote products derived from this software
  *      without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "i2c.h"

#include "gpio.h"

/* USER CODE BEGIN 0 */
#include "command_line.h"
#include "queue.h"
/* USER CODE END 0 */

I2C_HandleTypeDef hi2c1;
I2C_HandleTypeDef hi2c2;

/* I2C1 init function */
void MX_I2C1_Init(void)
{

  hi2c1.Instance = I2C1;
  hi2c1.Init.ClockSpeed = 100000;
  hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
  hi2c1.Init.OwnAddress1 = 0x34;
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c1.Init.OwnAddress2 = 0;
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c1) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

}
/* I2C2 init function */
void MX_I2C2_Init(void)
{

  hi2c2.Instance = I2C2;
  hi2c2.Init.ClockSpeed = 100000;
  hi2c2.Init.DutyCycle = I2C_DUTYCYCLE_2;
  hi2c2.Init.OwnAddress1 = 106;
  hi2c2.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c2.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c2.Init.OwnAddress2 = 0;
  hi2c2.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c2.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c2) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

}

void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct;
  if(i2cHandle->Instance==I2C1)
  {
  /* USER CODE BEGIN I2C1_MspInit 0 */

  /* USER CODE END I2C1_MspInit 0 */
  
    /**I2C1 GPIO Configuration    
    PB6     ------> I2C1_SCL
    PB7     ------> I2C1_SDA 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* I2C1 clock enable */
    __HAL_RCC_I2C1_CLK_ENABLE();
  /* USER CODE BEGIN I2C1_MspInit 1 */

  /* USER CODE END I2C1_MspInit 1 */
  }
  else if(i2cHandle->Instance==I2C2)
  {
  /* USER CODE BEGIN I2C2_MspInit 0 */

  /* USER CODE END I2C2_MspInit 0 */
  
    /**I2C2 GPIO Configuration    
    PB10     ------> I2C2_SCL
    PB11     ------> I2C2_SDA 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF4_I2C2;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* I2C2 clock enable */
    __HAL_RCC_I2C2_CLK_ENABLE();
  /* USER CODE BEGIN I2C2_MspInit 1 */

  /* USER CODE END I2C2_MspInit 1 */
  }
}

void HAL_I2C_MspDeInit(I2C_HandleTypeDef* i2cHandle)
{

  if(i2cHandle->Instance==I2C1)
  {
  /* USER CODE BEGIN I2C1_MspDeInit 0 */

  /* USER CODE END I2C1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_I2C1_CLK_DISABLE();
  
    /**I2C1 GPIO Configuration    
    PB6     ------> I2C1_SCL
    PB7     ------> I2C1_SDA 
    */
    HAL_GPIO_DeInit(GPIOB, GPIO_PIN_6|GPIO_PIN_7);

  /* USER CODE BEGIN I2C1_MspDeInit 1 */

  /* USER CODE END I2C1_MspDeInit 1 */
  }
  else if(i2cHandle->Instance==I2C2)
  {
  /* USER CODE BEGIN I2C2_MspDeInit 0 */

  /* USER CODE END I2C2_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_I2C2_CLK_DISABLE();
  
    /**I2C2 GPIO Configuration    
    PB10     ------> I2C2_SCL
    PB11     ------> I2C2_SDA 
    */
    HAL_GPIO_DeInit(GPIOB, GPIO_PIN_10|GPIO_PIN_11);

  /* USER CODE BEGIN I2C2_MspDeInit 1 */

  /* USER CODE END I2C2_MspDeInit 1 */
  }
} 

/* USER CODE BEGIN 1 */
//IPMI数据帧转化16进制
unsigned short str2HEX(unsigned char *src,unsigned char *des)
{
   	unsigned short i=0,j=1;
	unsigned char byte = 0;
	unsigned char *ptr = src;
	while(*ptr!='\0')
	{
		if(*ptr == ' ')
		{
			des[i++] = byte;
			byte = 0;
			j = 1;
			ptr++;//
		}
		else
		{
				if(*ptr >= 'a' && *ptr <= 'f')
					byte += (*ptr - '0'-39)*(16%(16+j)+16/(16+j));
				else if(*ptr >= 'A' && *ptr <= 'F')
					byte += (*ptr - '0'-7)*(16%(16+j)+16/(16+j));
			    else if((*ptr >='0') && (*ptr <= '9'))
					byte += (*ptr - '0')*(16%(16+j)+16/(16+j));
				if(j--<=0) j = 1;
				ptr++;
		}
 
 
		if(*ptr == '\0')//the last byte
		{
			des[i++] = byte;
		}
	}
	/*
	j = i;
	for(i=0;i<j;i++)
	{
		//printf("0x%02x ",des[i]);
	*/
	return i;
}

/**
 * @brief  I2C command line
 * @param  null
 * @retval null
 */
uint8_t CLI_IPMI(void *para, uint8_t len)
{
    uint8_t *pTemp,err;
		int cnt = 0,i = 0;
		uint8_t tempbuf[256]={0};
		uint8_t ipmimsg[256] = {0};
    pTemp = (uint8_t *)para;

    if ((0 < len) && (NULL != pTemp)) 
		{
        pTemp++; /* skip a blank space */
        if ('w' == *pTemp) 
				{
					++pTemp; /* skip a blank space */
					printf("\nipmi cmd:%s\r\n",++pTemp);
					while(*pTemp)
					{
						tempbuf[cnt++] = *pTemp;
						pTemp++; 
					}
					cnt = str2HEX(tempbuf,ipmimsg);
					printf("ipmi cmd data size [%d]\r\n",cnt-1);
					if(HAL_I2C_IsDeviceReady(&hi2c1,ipmimsg[0],3,50)==HAL_OK)
					{
						err = HAL_I2C_Mem_Write(&hi2c1,ipmimsg[0], 0, I2C_MEMADD_SIZE_8BIT ,&ipmimsg[1], cnt-1,200);
						if(!err)
						{
							NL1();
							DBG("ipmi write done\r\n");
						}
						else
						{
								printf("write i2c data err\r\n");      
								return False;
						}
					}
					else
					{
						DBG("i2c slave device not exits\r\n");
						return False;
					}
        } 
				else if ('r' == *pTemp) 
				{
						++pTemp; /* skip a blank space */
						printf("ipmi cmd:%s\r\n",++pTemp);
						while(*pTemp)
						{
							tempbuf[cnt++] = *pTemp;
							pTemp++;
						}
						cnt = str2HEX(tempbuf,ipmimsg);
						
						if(cnt>2)
						{
							printf("too more parameters\r\n");
							return False;
						}
						
						if(ipmimsg[1]>127)
						{
							printf("read len out of range,please modify len to read and try again!\r\n");
							return False;
						}
						
						if(HAL_I2C_IsDeviceReady(&hi2c1,ipmimsg[0],3,50)==HAL_OK)						
						{
							memset(tempbuf,0,sizeof(tempbuf));
							err = HAL_I2C_Mem_Read(&hi2c1,ipmimsg[0], 0, I2C_MEMADD_SIZE_8BIT, tempbuf, ipmimsg[1], 200);
							if(!err)
							{
								printf("read i2c data:");
								for(i=0;i<ipmimsg[1];i++)
								printf("0x%02x ",tempbuf[i]);
								printf("\r\n");
								NL1();
								DBG("i2c read done\r\n");
							}
							else
							{
								printf("read i2c data err\r\n");      
								return False;
							}
						}
						else
						{
							DBG("i2c slave device not exits\r\n");
							            
							return False;
						}
        } 
				else 
				{
            /* para. wrong */
            return False;
        }
    }

    return True;
}
/* USER CODE END 1 */

/**
  * @}
  */

/**
  * @}
  */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

不知道hal的库封装是怎样实现的,但是i2c发送读取只能调用HAL_I2C_Mem_Write和HAL_I2C_Mem_Read这两个函数才能正常发送和读取数据,但是想深入研究HAL库的同学,可以好好看看它的封装函数。

猜你喜欢

转载自blog.csdn.net/qq_28643619/article/details/90760837
今日推荐