之前在项目中用到了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
#include
#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;i2)
{
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
不知道hal的库封装是怎样实现的,但是i2c发送读取只能调用HAL_I2C_Mem_Write和HAL_I2C_Mem_Read这两个函数才能正常发送和读取数据,但是想深入研究HAL库的同学,可以好好看看它的封装函数。