简介
本节主要分析一个I2C驱动E2PROM程序。程序简单的完成中断方式对E2PROM进行写入和读出校验。
程序全文
cs
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xparameters_ps.h"
#include "xiicps.h"
#include "xscugic.h"
#define I2C_DEVICE_ID XPAR_PS7_I2C_0_DEVICE_ID
#define GIC_DEVICE_ID XPAR_PS7_SCUGIC_0_DEVICE_ID
#define I2C_INTR_ID XPAR_XIICPS_0_INTR
#define MAX_SIZE 32
#define PAGE_SIZE 16 //EEPROM 器件的页大小
#define EEPROM_START_ADDRESS 0 //读写起始地址
#define EEPROM_DEVICE_ADDRESS 0x50 //设备地址
#define SLV_MON_LOOP_COUNT 0x000FFFFF /< Slave Monitor Loop Count*/
#define IIC_SCLK_RATE 100000 //IIC 的时钟频率
volatile u8 TransmitComplete; /< Flag to check completion of Transmission */
volatile u8 ReceiveComplete; /< Flag to check completion of Reception */
volatile u32 TotalErrorCount; /< Total Error Count Flag */
volatile u32 SlaveResponse; /**< Slave Response Flag */
typedef u8 AddressType;
u8 WriteBuffer[sizeof(AddressType) + MAX_SIZE];
u8 ReadBuffer[MAX_SIZE];
XIicPs_Config Iic_Config;
XIicPs Iic_device;
XScuGic_Config Gic_Config;
XScuGic Gic_device;
u32 Iic_device_config (XIicPs_Config *XIicPs_ConfigPtr,XIicPs *Iic_devicePtr,u16 device_id);
u32 Iic_intr_config (XScuGic_Config *XScuGic_ConfigPtr,XScuGic *XScuGicPtr,XIicPs *Iic_devicePtr,u16 device_id,u32 intr_id);
static int FindEepromDevice(XIicPs *Iic_devicePtr,u16 device_address);
static int EepromWriteData(XIicPs *IicInstance, u16 ByteCount,u8 device_address);
static int EepromReadData(XIicPs *IicInstance, u8 *BufferPtr, u16 ByteCount,u16 device_address);
void Iic_intr_Handler(void *CallBackRef, u32 Event);
void Iic_rw_test(XIicPs *Iic_devicePtr,u16 device_address,u8 op_address);
int main()
{
Iic_device_config(&Iic_Config,&Iic_device,I2C_DEVICE_ID);
Iic_intr_config(&Gic_Config,&Gic_device,&Iic_device,GIC_DEVICE_ID,I2C_INTR_ID);
Iic_rw_test(&Iic_device,EEPROM_DEVICE_ADDRESS,EEPROM_START_ADDRESS);
for(;;);
}
u32 Iic_device_config(XIicPs_Config *XIicPs_ConfigPtr,XIicPs *Iic_devicePtr,u16 device_id)
{
XIicPs_ConfigPtr = XIicPs_LookupConfig(device_id);
XIicPs_CfgInitialize(Iic_devicePtr, XIicPs_ConfigPtr,XIicPs_ConfigPtr->BaseAddress);
XIicPs_SetSClk(Iic_devicePtr,IIC_SCLK_RATE);
}
u32 Iic_intr_config(XScuGic_Config *XScuGic_ConfigPtr,XScuGic *XScuGicPtr,XIicPs *Iic_devicePtr,u16 device_id,u32 intr_id)
{
XScuGic_ConfigPtr = XScuGic_LookupConfig(device_id);
XScuGic_CfgInitialize(XScuGicPtr, XScuGic_ConfigPtr, XScuGic_ConfigPtr->CpuBaseAddress);
XScuGic_Connect(XScuGicPtr, intr_id, (Xil_InterruptHandler) XIicPs_MasterInterruptHandler, (void *) Iic_devicePtr);
XIicPs_SetStatusHandler(Iic_devicePtr, (void *) Iic_devicePtr, Iic_intr_Handler);
XScuGic_Enable(XScuGicPtr, intr_id);
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_IRQ_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, XScuGicPtr);
Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ);
}
void Iic_intr_Handler(void CallBackRef, u32 Event)
{
/
All of the data transfer has been finished.
*/
if (0 != (Event & XIICPS_EVENT_COMPLETE_SEND)) {
TransmitComplete = TRUE;
} else if (0 != (Event & XIICPS_EVENT_COMPLETE_RECV)){
ReceiveComplete = TRUE;
} else if (0 != (Event & XIICPS_EVENT_SLAVE_RDY)) {
SlaveResponse = TRUE;
} else if (0 != (Event & XIICPS_EVENT_ERROR)){
TotalErrorCount++;
}
}
void Iic_rw_test(XIicPs *Iic_devicePtr,u16 device_address,u8 op_address)
{
int Status;
u32 Index;
AddressType Address = EEPROM_START_ADDRESS;
int PageSize,WrBfrOffset;
Status = FindEepromDevice(Iic_devicePtr,device_address);
if(Status != XST_SUCCESS)
{
xil_printf("Device NOT found! \n");
}
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
/*
Initialize the data to write and the read buffer.
/
WriteBuffer[0] = (u8) (Address);
WrBfrOffset = 1;
PageSize = PAGE_SIZE;
for (Index = 0; Index < PageSize; Index++) {
WriteBuffer[WrBfrOffset + Index] = 0xFF;
ReadBuffer[Index] = 0;
}
/
Write to the EEPROM.
*/
Status = EepromWriteData(Iic_devicePtr, WrBfrOffset + PageSize,device_address);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
/*
Read from the EEPROM.
*/
Status = EepromReadData(Iic_devicePtr, ReadBuffer, PageSize,device_address);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
/*
Verify the data read against the data written.
*/
for (Index = 0; Index < PageSize; Index++) {
if (ReadBuffer[Index] != WriteBuffer[Index + WrBfrOffset]) {
return XST_FAILURE;
}
}
WriteBuffer[0] = (u8) (Address);
WrBfrOffset = 1;
PageSize = PAGE_SIZE;
for (Index = 0; Index < PageSize; Index++) {
WriteBuffer[WrBfrOffset + Index] = Index + 10;
ReadBuffer[Index] = 0;
}
/*
Write to the EEPROM.
*/
Status = EepromWriteData(Iic_devicePtr, WrBfrOffset + PageSize,device_address);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
/*
Read from the EEPROM.
*/
Status = EepromReadData(Iic_devicePtr, ReadBuffer, PageSize,device_address);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
/*
Verify the data read against the data written.
*/
for (Index = 0; Index < PageSize; Index++) {
if (ReadBuffer[Index] != WriteBuffer[Index + WrBfrOffset]) {
return XST_FAILURE;
}
}
return XST_SUCCESS;
}
static int FindEepromDevice(XIicPs *Iic_devicePtr,u16 device_address)
{
int Index;
XIicPs *IicPtr = Iic_devicePtr;
SlaveResponse = FALSE;
XIicPs_DisableAllInterrupts(IicPtr->Config.BaseAddress);
XIicPs_EnableSlaveMonitor(IicPtr, device_address);
TotalErrorCount = 0;
Index = 0;
/*
Wait for the Slave Monitor Interrupt, the interrupt processing
works in the background, this function may get locked up in this
loop if the interrupts are not working correctly or the slave
never responds.
*/
while ((!SlaveResponse) && (Index < SLV_MON_LOOP_COUNT)) {
Index++;
/*
Ignore any errors. The hardware generates NACK interrupts
if the slave is not present.
*/
if (0 != TotalErrorCount) {
xil_printf("Test error unexpected NACK\n");
}
}
if (Index >= SLV_MON_LOOP_COUNT) {
XIicPs_DisableSlaveMonitor(IicPtr);
return XST_FAILURE;
}
XIicPs_DisableSlaveMonitor(IicPtr);
return XST_SUCCESS;
}
/***************************************************************************/
/
This function writes a buffer of data to the IIC serial EEPROM.
@param ByteCount contains the number of bytes in the buffer to be
written.
@return XST_SUCCESS if successful else XST_FAILURE.
@note The Byte count should not exceed the page size of the EEPROM as
noted by the constant PAGE_SIZE.
******************************************************************************/
static int EepromWriteData(XIicPs *IicInstance, u16 ByteCount,u8 device_address)
{
TransmitComplete = FALSE;
/*
Send the Data.
*/
XIicPs_MasterSend(IicInstance, WriteBuffer,
ByteCount, device_address);
/*
Wait for the entire buffer to be sent, letting the interrupt
processing work in the background, this function may get
locked up in this loop if the interrupts are not working
correctly.
*/
while (TransmitComplete == FALSE) {
if (0 != TotalErrorCount) {
return XST_FAILURE;
}
}
/*
Wait until bus is idle to start another transfer.
*/
while (XIicPs_BusIsBusy(IicInstance));
/*
Wait for a bit of time to allow the programming to complete
*/
usleep(250000);
return XST_SUCCESS;
}
/***************************************************************************/
/
This function reads data from the IIC serial EEPROM into a specified buffer.
@param BufferPtr contains the address of the data buffer to be filled.
@param ByteCount contains the number of bytes in the buffer to be read.
@return XST_SUCCESS if successful else XST_FAILURE.
@note None.
******************************************************************************/
static int EepromReadData(XIicPs *IicInstance, u8 BufferPtr, u16 ByteCount,u16 device_address)
{
int Status;
AddressType Address = EEPROM_START_ADDRESS;
int WrBfrOffset;
/
Position the Pointer in EEPROM.
*/
WriteBuffer[0] = (u8) (Address);
WrBfrOffset = 1;
Status = EepromWriteData(IicInstance, WrBfrOffset,device_address);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
ReceiveComplete = FALSE;
/*
Receive the Data.
*/
XIicPs_MasterRecv(IicInstance, BufferPtr,
ByteCount, device_address);
while (ReceiveComplete == FALSE) {
if (0 != TotalErrorCount) {
return XST_FAILURE;
}
}
/*
Wait until bus is idle to start another transfer.
*/
while (XIicPs_BusIsBusy(IicInstance));
return XST_SUCCESS;
}
主函数中首先进行I2C设备初始化,然后进行I2C中断初始化,最后进行读写测试。
程序流程分析
main函数
main函数包含三个函数分别配置I2C模块,I2C中断和进行I2C读写测试
Iic_device_config:I2C模块配置函数
Iic_intr_config:I2C中断配置函数
Iic_rw_test:I2C读写测试函数
Iic_device_config函数
Iic_device_config函数主要是用于配置I2C模块,实现I2C模块初始化
XIicPs_LookupConfig:寻找I2C设备
XIicPs_CfgInitialize:I2C初始化
XIicPs_SetSClk:设置I2C设备频率
Iic_intr_config函数
XScuGic_LookupConfig:寻找有效中断控制器
XScuGic_CfgInitialize:中断控制器初始化
XScuGic_Connect:IIC 外设中断号与驱动自带的标准处理函数 XUartPs_InterruptHandler 绑定。
XIicPs_SetStatusHandler:将中断发生时实际执行的函数注册为 Iic_intr_Handler
XScuGic_Enable:使能 GIC 中对应的 UART 中断线,允许外设中断进入 CPU
Xil_ExceptionRegisterHandler:注册 ARM 处理器异常处理入口(使能 IRQ 中断总开关)。
Xil_ExceptionEnableMask:使能 CPU 级中断响应。
Iic_rw_test函数
FindEepromDevice:确定I2C设备的设备地址
EepromWriteData:向E2PROM中写入数据
EepromReadData:从E2PROM中读出数据
相关函数分析
1.XIicPs_LookupConfig
-
函数概述: 该函数的主要作用是根据传入的 IIC 设备唯一 ID,在系统预先生成的 IIC 配置表
XIicPs_ConfigTable中遍历查找匹配项,并将找到的配置结构体指针返回给调用者。输入参数是 16 位无符号整型设备 ID(通常由xparameters.h定义),返回值是XIicPs_Config *类型的指针,若未找到对应设备则返回NULL。该函数常被驱动初始化流程首先调用,用于获取硬件静态参数(如基地址、时钟频率等),为后续的XIicPs_CfgInitialize提供配置数据。 -
语句整体分析 :函数体内先声明一个指向配置结构体的指针
CfgPtr并初始化为NULL,同时声明一个 32 位循环变量Index,然后用for循环从 0 遍历到系统 IIC 实例总数(宏XPAR_XIICPS_NUM_INSTANCES)减 1。循环体内依次比较当前表项XIicPs_ConfigTable[Index].DeviceId是否与传入值相等,一旦匹配则让CfgPtr指向该表项地址并用break终止循环,最后将CfgPtr返回(若遍历完仍未匹配,则CfgPtr保持NULL)。整个逻辑简洁高效,利用了静态配置表的线性查找,在多数系统中 IIC 控制器数量极少(通常只有一两个),因此不会成为性能瓶颈,同时返回NULL的约定便于上层函数进行错误处理。 -
逐句注释:
cpp
/*****************************************************************************/
/**
*
* @brief
* 根据唯一的设备 ID 查找设备配置信息。
* 该配置表中包含了系统中每个设备的配置信息。
*
* @param DeviceId 设备的 ID
*
* @return 指向配置结构体的指针,若系统中不存在指定设备则返回 NULL
*
* @note 无
*
******************************************************************************/
XIicPs_Config *XIicPs_LookupConfig(u16 DeviceId)
{
XIicPs_Config *CfgPtr = NULL; /* 定义配置指针并初始化为空 */
s32 Index; /* 定义循环索引变量 */
for (Index = 0; Index < XPAR_XIICPS_NUM_INSTANCES; Index++) {
/* 循环遍历系统中所有 IIC 实例 /
if (XIicPs_ConfigTable[Index].DeviceId == DeviceId) {
/ 若当前表项的 DeviceId 与传入参数匹配 /
CfgPtr = &XIicPs_ConfigTable[Index];
/ 让指针指向该表项地址 /
break; / 找到后立即退出循环 */
}
}
return (XIicPs_Config )CfgPtr; / 返回找到的配置指针,未找到则返回 NULL */
}
2.XIicPs_CfgInitialize
-
函数概述:
XIicPs_CfgInitialize用于初始化一个指定的XIicPs实例,使其驱动进入就绪可用状态。初始化后设备的默认状态为:设备禁用、从机模式。函数接收实例指针、配置结构体指针以及有效基地址,将配置信息复制到实例中,复位硬件,并设置默认回调与状态标志,最后返回XST_SUCCESS。该函数通常在查找配置表之后调用,是 IIC 驱动初始化流程的核心步骤。 -
语句整体分析 :函数首先通过
Xil_AssertNonvoid断言输入参数InstancePtr和ConfigPtr非空,保证后续操作安全。接着将ConfigPtr中的设备 ID、输入时钟频率复制到实例内部的Config结构体,并将传入的有效地址EffectiveAddr赋给BaseAddress字段;如果定义了XCLOCKING宏,还会复制参考时钟并清零时钟使能标志。随后设置默认的状态处理函数为StubHandler、回调引用为NULL,并将IsReady标记为组件就绪。之后调用XIicPs_Reset复位硬件,使设备进入初始状态。复位完成后读取并保存该实例支持的选项到Options字段,同时将重复启动标志IsRepeatedStart初始化为 0。最后返回成功状态码。整个过程完成了实例软件结构的填充和硬件的初始复位,为后续的设备配置和启动做好准备。
cpp
s32 XIicPs_CfgInitialize(XIicPs *InstancePtr, XIicPs_Config *ConfigPtr,
u32 EffectiveAddr)
{
/*
* 断言验证输入参数。
*/
Xil_AssertNonvoid(InstancePtr != NULL); /* 确保实例指针非空 */
Xil_AssertNonvoid(ConfigPtr != NULL); /* 确保配置指针非空 */
/*
* 设置一些默认值。
*/
InstancePtr->Config.DeviceId = ConfigPtr->DeviceId; /* 复制设备 ID */
InstancePtr->Config.BaseAddress = EffectiveAddr; /* 保存有效基地址 */
InstancePtr->Config.InputClockHz = ConfigPtr->InputClockHz;/* 复制输入时钟频率 */
#if defined (XCLOCKING)
InstancePtr->Config.RefClk = ConfigPtr->RefClk; /* 复制参考时钟(条件编译) /
InstancePtr->IsClkEnabled = 0; / 清零时钟使能标志 */
#endif
InstancePtr->StatusHandler = StubHandler; /* 设置默认状态处理函数为桩函数 */
InstancePtr->CallBackRef = NULL; /* 回调引用初始化为空 */
InstancePtr->IsReady = (u32)XIL_COMPONENT_IS_READY; /* 标记组件就绪 */
/*
复位 IIC 设备使其进入初始状态。预期设备配置将在本次初始化完成之后、
设备启动之前进行。
/
XIicPs_Reset(InstancePtr); / 复位 IIC 硬件 */
/*
保存该实例所具有的选项的副本。
/
InstancePtr->Options = XIicPs_GetOptions(InstancePtr); / 读取并保存选项 */
/* 将重复启动标志初始化为 0 /
InstancePtr->IsRepeatedStart = 0; / 清零重复启动标志 */
return (s32)XST_SUCCESS; /* 返回成功状态 */
}
3.XIicPs_Reset
-
函数概述 :XIicPs_Reset 用于复位 IIC 设备。该函数只能在驱动初始化完成后调用。复位后设备的配置状态与初始化后的状态相同,任何正在进行的数据传输都会被中止。调用此函数后,上层软件需要负责重新配置设备(如果需要)并重新使能中断。函数无返回值。
-
详细分析 :函数首先通过两个 Xil_AssertVoid 断言检查 InstancePtr 非空且驱动已就绪(IsReady == XIL_COMPONENT_IS_READY),确保复位操作在合法状态下进行。随后调用 XIicPs_Abort(InstancePtr) 中止当前正在进行的任何传输,防止复位过程中产生未定义行为。接着向控制寄存器(XIICPS_CR_OFFSET)写入复位默认值 XIICPS_CR_RESET_VALUE,使控制器回到初始状态(具体包括:从模式、10 位地址、写方向、无 ACK、无 HOLD、无 SLVMON、分频器为 0(最大 SCL 频率));向超时寄存器(XIICPS_TIME_OUT_OFFSET)写入复位值 XIICPS_TO_RESET_VALUE(0xFF);最后向中断禁用寄存器(XIICPS_IDR_OFFSET)写入全 1,即 XIICPS_IXR_ALL_INTR_MASK,禁用所有中断。这三步操作分别恢复了控制逻辑、超时设置和中断使能状态,使设备硬件与初始化后的软件状态一致。函数不更新实例中的任何软件字段,因此调用后需由上层代码重新配置中断等资源。
cpp
/*复位 IIC 设备。复位只能在驱动初始化之后调用。复位后设备的配置与初始化后的配置相同。
任何正在进行的数据传输都会被中止。
上层软件负责在复位后重新配置(如果需要)并重新使能 IIC 设备的中断。
@param InstancePtr 是指向 XIicPs 实例的指针。
@return 无。
@note 无。
*/
void XIicPs_Reset(XIicPs InstancePtr)
{
Xil_AssertVoid(InstancePtr != NULL); / 断言实例指针非空 /
Xil_AssertVoid(InstancePtr->IsReady == (u32)XIL_COMPONENT_IS_READY); / 断言驱动已就绪 */
/*
* 中止任何正在进行的传输。
*/
XIicPs_Abort(InstancePtr); /* 调用中止函数终止当前传输 */
/*
* 复位各寄存器值,使软件状态与硬件设备匹配。
*/
XIicPs_WriteReg(InstancePtr->Config.BaseAddress, XIICPS_CR_OFFSET,
XIICPS_CR_RESET_VALUE); /* 将控制寄存器写入复位默认值 */
XIicPs_WriteReg(InstancePtr->Config.BaseAddress,
XIICPS_TIME_OUT_OFFSET, XIICPS_TO_RESET_VALUE); /* 将超时寄存器写入复位默认值 */
XIicPs_WriteReg(InstancePtr->Config.BaseAddress, XIICPS_IDR_OFFSET,
XIICPS_IXR_ALL_INTR_MASK); /* 向中断禁用寄存器写入全 1,禁用所有中断 */
}
3.XIicPs_SetSClk
-
函数概述 :
XIicPs_SetSClk用于设置 IIC 设备的串行时钟(SCL)频率。该函数根据目标频率和输入时钟计算出控制寄存器中分频器 A 和 B 的值,使实际 SCL 频率尽可能接近指定值。设备必须处于空闲状态(未进行数据传输)才能设置,否则函数返回错误。常见的目标频率为 100 kHz 和 400 kHz,但受硬件限制,实际设置值会被调整为 90 kHz 和 384.6 kHz。成功设置后返回XST_SUCCESS。 -
整体分析: 函数首先通过断言检查实例指针有效、驱动已就绪以及目标频率大于零。随后读取传输大小寄存器,若其值非零则说明设备正在传输数据,立即返回
XST_DEVICE_IS_STARTED。接下来,函数先假设分频器 A 为 0,初步计算Temp = InputClockHz / (22 * FsclHz),该值近似等于(Div_a+1) * (Div_b+1)的乘积。若Temp为 0,说明目标频率超出硬件可支持范围,返回失败。由于硬件限制,函数对目标频率进行了修正:大于 384600 Hz 时取 384600 Hz;在 90000 Hz 到 100000 Hz 之间时取 90000 Hz。随后通过InputClockHz % (22 * FsclHz)判断是否需要将Temp的上限加 1,以便在循环中覆盖可能的更优解。算法采际频率和误差,记录全局误差最小的分频值。循环结束后,读取控制寄存器,清除原有的分频字段,写入新计算出的Div_a和Div_b,完成设置。整个算法通过枚举分频组合寻找最接近目标频率的配置,计算量较小,适合在嵌入式驱动用双重循环遍历:外层循环调整Temp(即乘积),内层循环遍历分频器 B(0~63),由Temp推导出分频器 A(范围 0~3)。对每个有效组合计算实中使用。
cs
/**
* @brief
* 该函数设置 IIC 设备的串行时钟速率。设备必须处于空闲状态而非忙于传输数据,
* 才能设置这些设备选项。
*
* 数据速率通过控制寄存器中的值来设置。确定正确寄存器值的公式为:
* Fscl = Fpclk / (22 × (divisor_a+1) × (divisor_b+1))
* 有关设置串行时钟速率的完整说明,请参见硬件数据手册。
*
* @param InstancePtr 是指向 XIicPs 实例的指针。
* @param FsclHz 是以 Hz 为单位的时钟频率。最常见的两种时钟速率是
* 100KHz 和 400KHz。
*
* @return
* - XST_SUCCESS 如果选项成功设置。
* - XST_DEVICE_IS_STARTED 如果设备当前正在传输数据。
* 必须等传输完成或中止后才能设置选项。
* - XST_FAILURE 如果无法设置 Fscl 频率。
*
* @note 时钟不能快于输入时钟除以 22。
*/
s32 XIicPs_SetSClk(XIicPs *InstancePtr, u32 FsclHz)
{
u32 Div_a; /* 分频器 A 的值 */
u32 Div_b; /* 分频器 B 的值 */
u32 ActualFscl; /* 实际计算得到的 SCL 频率 */
u32 Temp; /* 临时变量,保存 (Div_a+1)*(Div_b+1) 的初始估算值 */
u32 TempLimit; /* Temp 的迭代上限 */
u32 LastError; /* 当前 Temp 下的最小误差 */
u32 BestError; /* 全局最小误差 */
u32 CurrentError; /* 当前组合的误差 */
u32 ControlReg; /* 控制寄存器值 */
u32 CalcDivA; /* 当前 Temp 下的最佳 Div_a */
u32 CalcDivB; /* 当前 Temp 下的最佳 Div_b */
u32 BestDivA; /* 全局最佳 Div_a */
u32 BestDivB; /* 全局最佳 Div_b */
u32 FsclHzVar = FsclHz; /* 目标频率的局部可变副本 */
Xil_AssertNonvoid(InstancePtr != NULL); /* 断言实例指针非空 */
Xil_AssertNonvoid(InstancePtr->IsReady == (u32)XIL_COMPONENT_IS_READY); /* 断言驱动已就绪 */
Xil_AssertNonvoid(FsclHzVar > 0U); /* 断言频率大于 0 */
if (0U != XIicPs_In32((InstancePtr->Config.BaseAddress) +
XIICPS_TRANS_SIZE_OFFSET)) {
/* 如果传输大小寄存器非零,表示设备正在传输数据 */
return (s32)XST_DEVICE_IS_STARTED;
}
/*
* 假设 Div_a 为 0,计算 (divisor_a+1) x (divisor_b+1) 的值。
*/
Temp = (InstancePtr->Config.InputClockHz) / ((u32)22U * FsclHzVar);
/*
* 如果结果为 0,说明 Fscl 输入超出范围。
*/
if ((u32)(0U) == Temp) {
return (s32)XST_FAILURE;
}
/*
* 如果选择 400KHz,实际应设置 384.6KHz。
* 如果选择 100KHz,实际应设置 90KHz。
* 这是由硬件限制导致的。
*/
if (FsclHzVar > (u32)384600U) {
FsclHzVar = (u32)384600U;
}
if ((FsclHzVar <= (u32)100000U) && (FsclHzVar > (u32)90000U)) {
FsclHzVar = (u32)90000U;
}
/*
* TempLimit 用于在 Temp 的连续值上迭代,以找到最接近目标的分频值。
* 仅当存在小数部分时,才迭代下一个 Temp 值。
*/
TempLimit = (((InstancePtr->Config.InputClockHz) %
((u32)22 * FsclHzVar)) != (u32)0x0U) ?
(Temp + (u32)1U) : Temp;
BestError = FsclHzVar; /* 初始最佳误差设为目标频率 */
BestDivA = 0U; /* 初始化全局最佳 Div_a */
BestDivB = 0U; /* 初始化全局最佳 Div_b */
for ( ; Temp <= TempLimit ; Temp++) /* 外层循环:调整乘积值 */
{
LastError = FsclHzVar; /* 当前 Temp 下的初始误差 */
CalcDivA = 0U; /* 当前 Temp 下的最佳 Div_a */
CalcDivB = 0U; /* 当前 Temp 下的最佳 Div_b */
for (Div_b = 0U; Div_b < 64U; Div_b++) { /* 内层循环:遍历 Div_b */
Div_a = Temp / (Div_b + 1U); /* 由 Temp 和 Div_b 推导 Div_a */
if (Div_a != 0U) {
Div_a = Div_a - (u32)1U; /* 减 1 得到实际 Div_a */
}
if (Div_a > 3U) {
continue; /* Div_a 超出 2 位范围则跳过 */
}
ActualFscl = (InstancePtr->Config.InputClockHz) /
(22U * (Div_a + 1U) * (Div_b + 1U)); /* 计算实际频率 */
if (ActualFscl > FsclHzVar) {
CurrentError = (ActualFscl - FsclHzVar); /* 实际高于目标 */
} else {
CurrentError = (FsclHzVar - ActualFscl); /* 实际低于目标 */
}
if (LastError > CurrentError) {
CalcDivA = Div_a; /* 更新当前 Temp 下的最佳 Div_a */
CalcDivB = Div_b; /* 更新当前 Temp 下的最佳 Div_b */
LastError = CurrentError; /* 更新当前最小误差 */
}
}
/*
* 用于捕获全局最佳分频值。
*/
if (LastError < BestError) {
BestError = LastError;
BestDivA = CalcDivA;
BestDivB = CalcDivB;
}
}
/*
* 读取控制寄存器并屏蔽分频器字段。
*/
ControlReg = XIicPs_ReadReg(InstancePtr->Config.BaseAddress,
(u32)XIICPS_CR_OFFSET);
ControlReg &= ~((u32)XIICPS_CR_DIV_A_MASK | (u32)XIICPS_CR_DIV_B_MASK);
ControlReg |= (BestDivA << XIICPS_CR_DIV_A_SHIFT) |
(BestDivB << XIICPS_CR_DIV_B_SHIFT);
XIicPs_WriteReg(InstancePtr->Config.BaseAddress, (u32)XIICPS_CR_OFFSET,
ControlReg);
return (s32)XST_SUCCESS;
}
4.XIicPs_GetOptions
-
函数概述 :XIicPs_GetOptions 用于获取 IIC 设备当前的选项配置。这些选项控制设备在 IIC 总线上的行为。函数通过读取控制寄存器,结合预定义的选项映射表,生成一个 32 位的选项掩码并返回。掩码中某位为 1 表示对应选项被使能,0 表示禁用。地址模式(7 位/10 位)因共用同一个寄存器位,需特殊处理。此外,实例中的 IsRepeatedStart 标志也会反映到选项中。该函数通常在初始化后调用,以保存当前配置。
-
详细分析: 函数首先声明并初始化 OptionsFlag 为 0,然后读取 IIC 控制寄存器的当前值。接下来遍历一个全局选项表 OptionsTable(表中每一项包含掩码 Mask 和对应的选项位 Option),将控制寄存器值与每个掩码进行按位与,若结果非零则表示该选项被使能,将对应的 Option 位设置到 OptionsFlag 中。循环结束后,单独处理 NEA 位:如果 NEA 位为 0,表示 10 位地址模式,则设置 XIICPS_10_BIT_ADDR_OPTION 并清除 XIICPS_7_BIT_ADDR_OPTION;否则反之。这是因为 7 位和 10 位地址选项共用同一个硬件位(NEA),不能通过简单的掩码匹配来确定。最后,如果实例的 IsRepeatedStart 标志非零,则置位 XIICPS_REP_START_OPTION,返回组合后的选项掩码。整个函数仅进行软件层面的标志转换,不修改硬件状态。
cpp
/*
该函数获取 IIC 设备的选项。选项控制设备相对于 IIC 总线的行为。
@param InstancePtr 是指向 XIicPs 实例的指针。
@return 选项的 32 位掩码,其中 1 表示选项开启,0 表示选项关闭。
掩码中可能包含一个或多个位值。参见 xiicps.h 文件中名为
XIICPS_*_OPTION 的位定义。
@note 无。
*/
u32 XIicPs_GetOptions(XIicPs InstancePtr)
{
u32 OptionsFlag = 0U; / 初始化选项标志为 0 /
u32 ControlReg; / 保存控制寄存器值 /
u32 Index; / 循环索引 */
Xil_AssertNonvoid(InstancePtr != NULL); /* 断言实例指针非空 */
Xil_AssertNonvoid(InstancePtr->IsReady == (u32)XIL_COMPONENT_IS_READY); /* 断言驱动已就绪 */
/*
* 读取控制寄存器以确定当前设置了哪些选项。
*/
ControlReg = XIicPs_ReadReg(InstancePtr->Config.BaseAddress,
XIICPS_CR_OFFSET);
/* 从设备基地址读取控制寄存器 */
/*
* 遍历选项表以确定哪些选项被设置。
*/
for (Index = 0U; Index < XIICPS_NUM_OPTIONS; Index++) {
/* 循环遍历预定义的选项表 */
if ((ControlReg & OptionsTable[Index].Mask) != (u32)0x0U) {
/* 若控制寄存器中对应掩码位非零,表示该选项使能 */
OptionsFlag |= OptionsTable[Index].Option;
/* 将对应的选项位设置到 OptionsFlag 中 */
}
}
/*
* 单独处理 NEA 位,因为 10 位选项和 7 位选项使用相同的掩码。
*/
if ((ControlReg & XIICPS_CR_NEA_MASK) == (u32)0x0U) {
/* 若 NEA 位为 0,表示 10 位地址模式 */
OptionsFlag |= XIICPS_10_BIT_ADDR_OPTION; /* 设置 10 位地址选项 */
OptionsFlag &= ~(XIICPS_7_BIT_ADDR_OPTION); /* 清除 7 位地址选项 */
}
else {
/* 否则为 7 位地址模式 */
OptionsFlag |= XIICPS_7_BIT_ADDR_OPTION; /* 设置 7 位地址选项 */
OptionsFlag &= ~(XIICPS_10_BIT_ADDR_OPTION); /* 清除 10 位地址选项 */
}
if (InstancePtr->IsRepeatedStart != 0) {
/* 若实例中重复启动标志非零 */
OptionsFlag |= XIICPS_REP_START_OPTION; /* 设置重复启动选项 */
}
return OptionsFlag; /* 返回最终选项掩码 */
}
4.XScuGic_LookupConfig
- 函数 ****概述:****XScuGic_LookupConfig 用于根据唯一的设备 ID 在系统的 GIC 中断控制器配置表 XScuGic_ConfigTable 中查找对应的配置信息。它遍历所有可用的 SCU GIC 实例(数量由 XPAR_SCUGIC_NUM_INSTANCES 定义),将每个表项的 DeviceId 与传入参数比较,匹配则返回指向该配置项的指针,未找到则返回 NULL。该函数通常在驱动初始化阶段首先调用,以获取硬件基地址、中断 ID 范围等静态参数,为后续的 XScuGic_CfgInitialize 提供数据。
- 详细分析:函数首先将局部指针 CfgPtr 初始化为 NULL,保证查找失败时能安全返回空指针。随后进入 for 循环,循环变量 Index 为 32 位无符号整数,从 0 递增至 XPAR_SCUGIC_NUM_INSTANCES - 1,逐一访问全局配置表 XScuGic_ConfigTable 的元素。在循环体内,通过 if 条件比较 XScuGic_ConfigTableIndex.DeviceId 与传入的 DeviceId 是否相等,一旦匹配,就将 CfgPtr 指向该表项的地址,并立即执行 break 跳出循环,避免不必要的后续比较。循环结束后直接返回 CfgPtr。由于配置表是静态分配的,返回的指针指向静态存储区,生命周期与程序相同,调用者不应修改或释放。该查找算法为线性搜索,时间复杂度 O(N),在大多数嵌入式系统中 GIC 实例数极少(通常仅一个),因此性能完全可接受。该函数与 Xilinx 其他外设驱动的 LookupConfig 函数实现模式一致。
cs
/**
* @brief
* 根据唯一的设备 ID 查找设备配置。表中包含系统中每个设备的配置信息。
*
* @param DeviceId 是设备的唯一标识符。
*
* @return 指向指定设备的 XScuGic 配置结构体的指针;
* 如果未找到该设备则返回 NULL。
*
* @note 无。
*/
XScuGic_Config *XScuGic_LookupConfig(u16 DeviceId)
{
XScuGic_Config *CfgPtr = NULL; /* 初始化配置指针为 NULL */
u32 Index; /* 声明循环索引变量 */
for (Index = 0U; Index < (u32)XPAR_SCUGIC_NUM_INSTANCES; Index++) {
/* 遍历系统中所有 SCU GIC 实例的配置表 */
if (XScuGic_ConfigTable[Index].DeviceId == DeviceId) {
/* 若当前表项的 DeviceId 与传入参数匹配 */
CfgPtr = &XScuGic_ConfigTable[Index];
/* 让指针指向该表项地址 */
break; /* 找到后立即退出循环 */
}
}
return (XScuGic_Config *)CfgPtr; /* 返回找到的配置指针,未找到则返回 NULL */
}
5.XScuGic_CfgInitialize
函数概述 ****:****XScuGic_CfgInitialize 用于初始化一个特定的 XScuGic 中断控制器实例,使其驱动进入就绪状态。初始化过程包括:填充 XScuGic 结构体的配置字段、将中断向量表中所有未连接的中断处理函数设置为桩函数(StubHandler)并将回调引用指向本实例、停止控制器、初始化分发器(Distributor)以及 CPU 接口(对于非 GICv3 架构),最后将实例标记为就绪。该函数通常在 XScuGic_LookupConfig 之后调用,为后续的中断连接和使能做好准备。
整体分析 ****:****函数首先通过 (void) EffectiveAddr; 显式忽略 EffectiveAddr 参数(因为该实现中实际使用 ConfigPtr 中的基地址,参数仅为兼容接口保留),然后使用断言检查 InstancePtr 和 ConfigPtr 非空。在 ARMA9 编译条件下,还会检查 CPU ID 是否为 1,若是则进一步通过 eFUSE 状态确认当前芯片不是单核配置,以防止在单核系统中错误使用 CPU1。接下来判断 InstancePtr->IsReady 是否不等于 XIL_COMPONENT_IS_READY,若尚未就绪则执行初始化:先将 IsReady 置 0,将 Config 指向传入的配置表,然后遍历所有中断输入(0 到 XSCUGIC_MAX_NUM_INTR_INPUTS - 1),对于每个中断,如果其处理函数为 NULL,则赋值为 StubHandler,并将回调引用设置为当前实例。之后根据是否定义了 GICv3 执行不同的分支:若为 GICv3,则唤醒 redistributor、使能系统寄存器接口(EL3 和 EL1)并执行指令屏障;否则跳过该部分。随后统一调用 XScuGic_Stop 停止控制器,调用 DistributorInit 初始化分发器;GICv3 架构下设置优先级过滤器为 0xff,非 GICv3 架构下调用 CPUInitialize 初始化 CPU 接口。最后将 IsReady 置为就绪状态并返回成功。整个初始化过程确保了中断控制器硬件和软件结构均处于可用的初始状态。
cs
/*初始化一个特定的中断控制器实例/驱动。初始化包括:
初始化 XScuGic 结构体的字段
用桩函数调用初始化向量表
禁用所有中断源
@param InstancePtr 是指向 XScuGic 实例的指针。
@param ConfigPtr 是指向该驱动关联的特定设备的配置表的指针。
@param EffectiveAddr 是设备在虚拟内存地址空间中的基地址。调用者负责在调用此函数后保持
EffectiveAddr 到设备物理基地址的地址映射不变。如果地址映射在调用后发生变化,
可能会出现意外错误。如果不使用地址转换,则使用 Config->BaseAddress 作为此参数,
传入物理地址。
@return XST_SUCCESS 如果初始化成功
@note 无。
*/
s32 XScuGic_CfgInitialize(XScuGic *InstancePtr,
XScuGic_Config ConfigPtr,
u32 EffectiveAddr)
{
u32 Int_Id; / 循环变量,用于遍历所有中断输入 /
(void) EffectiveAddr; / 显式忽略 EffectiveAddr 参数,避免编译器警告 */
Xil_AssertNonvoid(InstancePtr != NULL); /* 断言实例指针非空 */
Xil_AssertNonvoid(ConfigPtr != NULL); /* 断言配置指针非空 */
/*
* 检查 Zynq-7000 基础硅配置。
* 如果是单核配置,则对 CPU ID 1 触发断言。
*/
#ifdef ARMA9
if (XPAR_CPU_ID == 0x01) {
Xil_AssertNonvoid((Xil_In32(XPS_EFUSE_BASEADDR
+ EFUSE_STATUS_OFFSET) & EFUSE_STATUS_CPU_MASK) == 0);
}
#endif
if (InstancePtr->IsReady != XIL_COMPONENT_IS_READY) { /* 若实例尚未就绪,则执行初始化 */
InstancePtr->IsReady = 0U; /* 将就绪标志暂时清零,表示初始化进行中 */
InstancePtr->Config = ConfigPtr; /* 将配置指针保存到实例中 */
for (Int_Id = 0U; Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS; Int_Id++) {
/*
* 将处理函数初始化为指向桩函数,以处理尚未连接到处理函数的中断。
* 仅当处理函数为 0(表示未由工具/用户静态初始化)时才进行初始化。
* 将回调引用设置为本实例,以便跟踪未处理的中断。
*/
if ((InstancePtr->Config->HandlerTable[Int_Id].Handler
== (Xil_InterruptHandler)NULL)) {
InstancePtr->Config->HandlerTable[Int_Id].Handler
= (Xil_InterruptHandler)StubHandler;
}
InstancePtr->Config->HandlerTable[Int_Id].CallBackRef = InstancePtr;
}
#if defined (GICv3)
u32 Waker_State;
Waker_State = XScuGic_ReDistReadReg(InstancePtr, XSCUGIC_RDIST_WAKER_OFFSET);
XScuGic_ReDistWriteReg(InstancePtr, XSCUGIC_RDIST_WAKER_OFFSET,
Waker_State & (~XSCUGIC_RDIST_WAKER_LOW_POWER_STATE_MASK));
/* 通过 ICC_SRE_EL1 使能系统寄存器接口 */
#if EL3
XScuGic_Enable_SystemReg_CPU_Interface_EL3();
#endif
XScuGic_Enable_SystemReg_CPU_Interface_EL1();
isb();
#endif
XScuGic_Stop(InstancePtr); /* 停止控制器,确保其处于初始状态 */
DistributorInit(InstancePtr); /* 初始化分发器(Distributor) */
#if defined (GICv3)
XScuGic_set_priority_filter(0xff); /* GICv3:设置优先级过滤器 /
#else
CPUInitialize(InstancePtr); / 非 GICv3:初始化 CPU 接口 */
#endif
InstancePtr->IsReady = XIL_COMPONENT_IS_READY; /* 标记实例为就绪 */
}
return XST_SUCCESS; /* 返回成功 */
}
6.XScuGic_Connect
函数概述:XScuGic_Connect 用于将指定的中断源 ID(Int_Id)与对应的中断处理函数(Handler)建立连接,同时保存回调引用(CallBackRef)。当该中断被识别时,驱动会调用连接的处理函数,并将 CallBackRef 作为参数传入。该函数通过将 Int_Id 作为索引直接写入实例配置中的 HandlerTable 来实现连接,操作简单高效。注意:如果该中断源之前已经连接过处理函数,本次调用会覆盖原有连接。
详细 分析:函数首先使用四个断言检查输入参数的有效性:InstancePtr 非空、Int_Id 小于最大中断输入数、Handler 非空、实例已就绪。这些断言保证了后续对 HandlerTable 的索引访问安全且驱动处于可用状态。随后直接以 Int_Id 为下标,将传入的 Handler 和 CallBackRef 分别写入 InstancePtr->Config->HandlerTableInt_Id 的对应字段。整个操作没有任何条件分支或硬件访问,仅更新软件数据结构,因此执行速度快,且不会影响中断控制器的当前使能状态。由于没有检查原处理函数是否已存在,所以会无条件覆盖旧连接,这正是注释中警告的内容。函数最后返回 XST_SUCCESS 表示连接成功。
cs
/*在中断源的 Int_Id 与中断被识别时要运行的关联处理函数之间建立连接。
本次调用中作为 Callbackref 提供的参数将作为处理函数被调用时的参数。
@param InstancePtr 是指向 XScuGic 实例的指针。
@param Int_Id 包含中断源的 ID,应在 0 到 XSCUGIC_MAX_NUM_INTR_INPUTS - 1 的范围内。
@param Handler 该中断的处理函数。
@param CallBackRef 回调引用,通常是连接驱动的实例指针。
@return
XST_SUCCESS 如果处理函数连接正确。
@note
警告:作为参数提供的处理函数将覆盖任何之前已连接的处理函数。
*/
s32 XScuGic_Connect(XScuGic *InstancePtr, u32 Int_Id,
Xil_InterruptHandler Handler, void CallBackRef)
{
/
* 断言参数
/
Xil_AssertNonvoid(InstancePtr != NULL); / 确保实例指针非空 /
Xil_AssertNonvoid(Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS); / 确保中断 ID 在有效范围内 /
Xil_AssertNonvoid(Handler != NULL); / 确保处理函数指针非空 /
Xil_AssertNonvoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY); / 确保驱动已就绪 */
/*
* Int_Id 用作表索引来选择正确的处理函数
*/
InstancePtr->Config->HandlerTable[Int_Id].Handler = (Xil_InterruptHandler)Handler;
/* 将处理函数指针写入 HandlerTable 的对应项 */
InstancePtr->Config->HandlerTable[Int_Id].CallBackRef = CallBackRef;
/* 将回调引用写入 HandlerTable 的对应项 */
return XST_SUCCESS; /* 返回成功状态 */
}
7.XIicPs_SetStatusHandler
函数概述 ****:****XIicPs_SetStatusHandler 用于设置状态回调函数(状态处理器),驱动在遇到需要向高层软件报告的条件时会调用该回调。该回调在中断上下文中执行,因此处理量应尽量最小化。函数接收实例指针、回调引用以及回调函数指针,将回调函数和引用保存到实例结构中,供驱动后续事件通知使用。无返回值。
详细分析:函数首先通过三个 Xil_AssertVoid 断言检查输入参数:InstancePtr 非空、FunctionPtr 非空、实例已就绪(IsReady == XIL_COMPONENT_IS_READY)。这些断言确保驱动处于可配置状态且回调函数有效。随后将传入的回调函数指针 FunctionPtr 赋给实例的 StatusHandler 字段,将 CallBackRef 赋给实例的 CallBackRef 字段。这样当驱动检测到需要报告的状态事件(如总线错误、传输完成等)时,会调用 StatusHandler 并将 CallBackRef 作为第一个参数传入,从而让上层软件能够识别事件来源并进行相应处理。由于回调在中断上下文中被调用,注释特别强调处理应快速完成,避免长时间占用中断。回调事件类型在 xiicps.h 中以 XIICPS_EVENT_* 宏定义,上层软件可根据事件类型进行分支处理。
cs
/*
该函数设置状态回调函数(即状态处理器),驱动在遇到需要向高层软件报告的条件时会调用该回调。
该处理器在中断上下文中执行,因此处理量应尽量减少。
有关回调事件的列表,请参见 xiicps.h 文件。事件以 XIICPS_EVENT_* 开头定义。
@param InstancePtr 是指向 XIicPs 实例的指针。
@param CallBackRef 是上层回调引用,在回调函数被调用时传回。
@param FunctionPtr 是指向回调函数的指针。
@return 无。
@note
处理器在中断上下文中被调用,因此应快速完成其工作。
*/
void XIicPs_SetStatusHandler(XIicPs *InstancePtr, void CallBackRef,
XIicPs_IntrHandler FunctionPtr)
{
Xil_AssertVoid(InstancePtr != NULL); / 断言实例指针非空 /
Xil_AssertVoid(FunctionPtr != NULL); / 断言回调函数指针非空 /
Xil_AssertVoid(InstancePtr->IsReady == (u32)XIL_COMPONENT_IS_READY); / 断言驱动已就绪 */
InstancePtr->StatusHandler = FunctionPtr; /* 保存状态回调函数指针 */
InstancePtr->CallBackRef = CallBackRef; /* 保存回调引用 */
}
8.XIicPs_MasterInterruptHandler
- 函数概述 : XIicPs_MasterInterruptHandler 是 IIC 主模式下的中断处理函数,负责处理中断驱动的数据传输协议。它响应数据有效(DATA)、传输完成(COMP)、从设备就绪(SLV_RDY)、NACK、仲裁丢失、超时等中断事件,根据当前是发送还是接收状态执行相应的 FIFO 填充或读取操作,管理传输大小更新和 HOLD 位控制,最后将事件通过状态回调函数通知上层软件。该函数通常在中断服务程序中被调用,是 IIC 主模式传输的核心状态机。
- 详细分析 : 函数首先断言实例有效且就绪,然后读取中断状态寄存器 ISR 并将原值写回以清除中断,接着读取中断掩码寄存器 IMR 并对 ISR 取反与,从而只保留已使能的中断。然后获取当前字节计数 CurrByteCount、HOLD 位状态和平台信息。接下来分两个主要分支处理发送和接收:
发送分支: 若 IsSend 为真且 COMP 中断发生,则检查 SendByteCount 是否还有剩余字节;若有,调用 MasterSendData 继续发送;否则设置 XIICPS_EVENT_COMPLETE_SEND 事件。
接收分支: 若 IsSend 为假且 DATA 或 COMP 中断发生,则进入 while 循环,只要接收 FIFO 有数据就读取。在 Zynq 平台上,当剩余字节数小于 XIICPS_DATA_INTR_DEPTH、HOLD位有效、非重复启动且无传输大小更新时,清除 HOLD 位。然后调用 XIicPs_RecvByte 读取一个字节并递减 ByteCnt。如果平台是 Zynq 且需要更新传输大小(UpdateTxSize 为真),当 ByteCnt 等于 FIFO_DEPTH + 1 时跳出循环,后续等待 FIFO 满后更新传输大小寄存器,并相应调整 ByteCnt。
之后处理完成事件: 若接收且COMP 中断,且 RecvByteCount 已为 0,则清除 HOLD 位(除非重复启动)并设置 XIICPS_EVENT_COMPLETE_RECV 事件。然后依次检查 SLV_RDY、NACK、ARB_LOST、TO 等中断标志,分别设置对应事件,并在 NACK 或错误时清除 HOLD 位(除非重复启动)。最后若 StatusEvent 非零,调用实例的状态处理函数 StatusHandler 通知上层。
整个处理过程涉及多个硬件寄存器操作和状态标志维护,确保主模式传输能够正确处理各种中断条件和错误情况。
cpp
/*主模式的中断处理函数。它为中断驱动的传输执行协议处理。
完成事件和错误通过信号通知上层以便正确处理。
处理的中断包括:
DATA
该情况仅针对主接收数据。主设备需要请求更多数据(如果有更多数据要接收)并从 FIFO 读取数据。
COMP
如果主设备正在发送数据且还有更多数据要发送,则将数据写入 FIFO。如果没有更多数据要发送,则通过调用回调处理程序向高层发出完成事件。
如果主设备正在接收数据,则从 FIFO 读取数据,并且主设备需要请求更多数据(如果有更多数据要接收)。如果所有数据已接收完毕,则通过调用回调处理程序向高层发出完成事件。
如果接收到的数据量超过预期,则为错误。
NAK 和 SLAVE_RDY
通过调用回调处理程序向上层发出信号。
所有其他中断
这些中断被标记为错误。通过调用回调处理程序向上层发出信号。
@param InstancePtr 是指向 XIicPs 实例的指针。
@return 无。
@note 无。
*/
void XIicPs_MasterInterruptHandler(XIicPs InstancePtr)
{
u32 IntrStatusReg; / 中断状态寄存器值 /
u32 StatusEvent = 0U; / 需要通知上层的事件掩码 /
u32 BaseAddr; / 设备基地址 /
u16 SlaveAddr; / 从设备地址(用于非 Zynq 分支重新启动传输) /
s32 ByteCnt; / 当前剩余字节计数 /
s32 IsHold; / 标志:HOLD 位是否有效 /
u32 Platform; / 当前平台信息 */
/*
* 断言验证输入参数。
*/
Xil_AssertVoid(InstancePtr != NULL); /* 实例非空 */
Xil_AssertVoid(InstancePtr->IsReady == (u32)XIL_COMPONENT_IS_READY); /* 驱动就绪 */
BaseAddr = InstancePtr->Config.BaseAddress; /* 获取基地址 */
Platform = XGetPlatform_Info(); /* 获取平台信息 */
/*
* 读取中断状态寄存器。
*/
IntrStatusReg = XIicPs_ReadReg(BaseAddr, (u32)XIICPS_ISR_OFFSET);
/*
* 将状态写回以清除中断,避免处理过程中丢失事件。
*/
XIicPs_WriteReg(BaseAddr, (u32)XIICPS_ISR_OFFSET, IntrStatusReg);
/*
* 使用掩码寄存器与中断状态寄存器进行与运算,以便不处理被禁用的中断。
*/
IntrStatusReg &= ~(XIicPs_ReadReg(BaseAddr, (u32)XIICPS_IMR_OFFSET));
ByteCnt = InstancePtr->CurrByteCount; /* 获取当前传输字节计数 */
IsHold = 0;
if ((XIicPs_ReadReg(BaseAddr, (u32)XIICPS_CR_OFFSET) & (u32)XIICPS_CR_HOLD_MASK) != 0U) {
IsHold = 1; /* 检查 HOLD 位是否置位 */
}
/*
* 发送处理
*/
if (((InstancePtr->IsSend) != 0) &&
((u32)0U != (IntrStatusReg & (u32)XIICPS_IXR_COMP_MASK))) {
/* 若为发送且发生传输完成中断 */
if (InstancePtr->SendByteCount > 0) {
MasterSendData(InstancePtr); /* 还有数据,继续填充 FIFO 发送 */
} else {
StatusEvent |= XIICPS_EVENT_COMPLETE_SEND; /* 发送完成,设置事件 */
}
}
/*
* 接收处理
*/
if ((InstancePtr->IsSend == 0) &&
((0U != (IntrStatusReg & (u32)XIICPS_IXR_DATA_MASK)) ||
(0U != (IntrStatusReg & (u32)XIICPS_IXR_COMP_MASK)))) {
/* 若为接收且发生数据有效或传输完成中断 */
while ((XIicPs_RxDataValid(InstancePtr)) != 0U) {
/* 只要接收 FIFO 中有有效数据就循环读取 */
if ((InstancePtr->RecvByteCount < XIICPS_DATA_INTR_DEPTH) &&
(IsHold != 0) &&
(InstancePtr->IsRepeatedStart == 0) &&
(InstancePtr->UpdateTxSize == 0)) {
/* 若剩余字节数较少、HOLD 有效、非重复启动、无传输大小更新 */
IsHold = 0;
XIicPs_WriteReg(BaseAddr, XIICPS_CR_OFFSET,
XIicPs_ReadReg(BaseAddr, XIICPS_CR_OFFSET) &
(~XIICPS_CR_HOLD_MASK));
/* 清除 HOLD 位,以便接收完成后正常产生完成中断 */
}
XIicPs_RecvByte(InstancePtr); /* 从 FIFO 读一个字节 */
ByteCnt--; /* 剩余字节数减 1 */
if (Platform == (u32)XPLAT_ZYNQ) {
if ((InstancePtr->UpdateTxSize != 0) &&
(ByteCnt == (XIICPS_FIFO_DEPTH + 1))) {
break; /* Zynq 下当剩余字节数为 FIFO 深度+1 时跳出,等待处理传输大小更新 */
}
}
}
if (Platform == (u32)XPLAT_ZYNQ) {
if ((InstancePtr->UpdateTxSize != 0) &&
(ByteCnt == (XIICPS_FIFO_DEPTH + 1))) {
/* Zynq 下需要更新传输大小 */
while (XIicPs_RxFIFOFull(InstancePtr, ByteCnt) != 0U) { ; }
/* 等待 FIFO 达到满状态 */
if ((InstancePtr->RecvByteCount - XIICPS_FIFO_DEPTH) >
(s32)XIICPS_MAX_TRANSFER_SIZE) {
/* 如果剩余接收量超过最大传输大小 */
XIicPs_WriteReg(BaseAddr, XIICPS_TRANS_SIZE_OFFSET,
XIICPS_MAX_TRANSFER_SIZE);
ByteCnt = (s32)XIICPS_MAX_TRANSFER_SIZE + XIICPS_FIFO_DEPTH;
} else {
/* 否则写入实际剩余量 */
XIicPs_WriteReg(BaseAddr, XIICPS_TRANS_SIZE_OFFSET,
InstancePtr->RecvByteCount - XIICPS_FIFO_DEPTH);
InstancePtr->UpdateTxSize = 0;
ByteCnt = InstancePtr->RecvByteCount;
}
}
} else {
if ((InstancePtr->RecvByteCount > 0) && (ByteCnt == 0)) {
/* 非 Zynq 下,当当前传输字节计数减到 0 但仍需接收时,重新启动传输 */
IntrStatusReg = XIicPs_ReadReg(BaseAddr, XIICPS_ISR_OFFSET);
XIicPs_WriteReg(BaseAddr, XIICPS_ISR_OFFSET, IntrStatusReg);
/* 清中断状态 */
SlaveAddr = (u16)XIicPs_ReadReg(BaseAddr, (u32)XIICPS_ADDR_OFFSET);
XIicPs_WriteReg(BaseAddr, XIICPS_ADDR_OFFSET, SlaveAddr);
/* 重新写入从地址以启动下一次传输 */
if ((InstancePtr->RecvByteCount) > (s32)XIICPS_MAX_TRANSFER_SIZE) {
XIicPs_WriteReg(BaseAddr, XIICPS_TRANS_SIZE_OFFSET,
XIICPS_MAX_TRANSFER_SIZE);
ByteCnt = (s32)XIICPS_MAX_TRANSFER_SIZE;
} else {
XIicPs_WriteReg(BaseAddr, XIICPS_TRANS_SIZE_OFFSET,
InstancePtr->RecvByteCount);
InstancePtr->UpdateTxSize = 0;
ByteCnt = InstancePtr->RecvByteCount;
}
XIicPs_EnableInterrupts(BaseAddr,
(u32)XIICPS_IXR_NACK_MASK | (u32)XIICPS_IXR_DATA_MASK |
(u32)XIICPS_IXR_RX_OVR_MASK | (u32)XIICPS_IXR_COMP_MASK |
(u32)XIICPS_IXR_ARB_LOST_MASK);
/* 重新使能相关中断 */
}
}
InstancePtr->CurrByteCount = ByteCnt; /* 更新当前字节计数 */
}
if ((InstancePtr->IsSend == 0) &&
(0U != (IntrStatusReg & XIICPS_IXR_COMP_MASK))) {
/* 若接收且发生传输完成中断 */
if (InstancePtr->RecvByteCount == 0) {
/* 所有数据接收完成 */
if (InstancePtr->IsRepeatedStart == 0) {
XIicPs_WriteReg(BaseAddr, XIICPS_CR_OFFSET,
XIicPs_ReadReg(BaseAddr, XIICPS_CR_OFFSET) &
(~XIICPS_CR_HOLD_MASK));
/* 清除 HOLD 位 */
}
StatusEvent |= XIICPS_EVENT_COMPLETE_RECV; /* 设置接收完成事件 */
}
}
/*
* 从设备就绪中断,仅在主模式下有意义。
*/
if (0U != (IntrStatusReg & XIICPS_IXR_SLV_RDY_MASK)) {
StatusEvent |= XIICPS_EVENT_SLAVE_RDY;
}
if (0U != (IntrStatusReg & XIICPS_IXR_NACK_MASK)) {
/* 发生 NACK */
if (InstancePtr->IsRepeatedStart == 0) {
XIicPs_WriteReg(BaseAddr, XIICPS_CR_OFFSET,
XIicPs_ReadReg(BaseAddr, XIICPS_CR_OFFSET) &
(~XIICPS_CR_HOLD_MASK));
/* 清除 HOLD 位 */
}
StatusEvent |= XIICPS_EVENT_NACK;
}
/*
* 仲裁丢失中断
*/
if (0U != (IntrStatusReg & XIICPS_IXR_ARB_LOST_MASK)) {
StatusEvent |= XIICPS_EVENT_ARB_LOST;
}
if (0U != (IntrStatusReg & XIICPS_IXR_TO_MASK)) {
XIicPs_DisableInterrupts(BaseAddr, XIICPS_IXR_TO_MASK);
/* 禁用超时中断 */
StatusEvent |= XIICPS_EVENT_TIME_OUT;
}
/*
* 所有其他中断都视为错误。
*/
if (0U != (IntrStatusReg & (XIICPS_IXR_NACK_MASK |
XIICPS_IXR_RX_UNF_MASK | XIICPS_IXR_TX_OVR_MASK |
XIICPS_IXR_RX_OVR_MASK))) {
if (InstancePtr->IsRepeatedStart == 0) {
XIicPs_WriteReg(BaseAddr, XIICPS_CR_OFFSET,
XIicPs_ReadReg(BaseAddr, XIICPS_CR_OFFSET) &
(~XIICPS_CR_HOLD_MASK));
}
StatusEvent |= XIICPS_EVENT_ERROR;
}
/*
* 如果有任何事件,通知应用程序。
*/
if (StatusEvent != 0U) {
InstancePtr->StatusHandler(InstancePtr->CallBackRef, StatusEvent);
}
}
9.XScuGic_Enable
函数概述 ****:****XScuGic_Enable 用于使能由 Int_Id 指定的中断源。调用该函数后,该中断源的任何挂起中断条件将被 GIC 识别并传递给 CPU。此 API 同时负责将中断映射到当前请求的 CPU。函数通过操作 GIC 分发器(Distributor)的 Enable Set 寄存器来使能 SPI 中断,对于 GICv3 架构下的 SGI/PPI 中断,则额外操作 redistributor 的 ISENABLE 寄存器。通常在 XScuGic_Connect 连接好处理函数后调用,以使中断生效。
详细分析:函数首先通过断言检查 InstancePtr 非空、Int_Id 在有效范围内(0 到 XSCUGIC_MAX_NUM_INTR_INPUTS - 1)以及驱动已就绪(IsReady == XIL_COMPONENT_IS_READY)。然后获取当前 CPU ID(Cpu_Id),用于中断到 CPU 的映射。在 GICv3 架构下(defined (GICv3)),如果 Int_Id 小于 XSCUGIC_SPI_INT_ID_START(即属于 SGI 或 PPI),函数会执行额外步骤:先调用 XScuGic_InterruptMaptoCpu 将该中断映射到当前 CPU,然后将 Int_Id 截断到低 5 位(实际 SGI/PPI 只有 32 个),生成对应位掩码,并直接操作 redistributor 的 ISENABLE 寄存器使能该中断。这一步是因为 GICv3 中 SGI/PPI 的使能控制位于 redistributor 而非 distributor。随后,对于所有类型的中断(包括 SGI/PPI 和 SPI),函数都会再次调用 XScuGic_InterruptMaptoCpu 进行 CPU 映射(对 SGI/PPI 可能重复,但无害),并最终通过计算 Mask = 1U << (Int_Id % 32U) 和写 distributor 的 Enable Set 寄存器(偏移由 Int_Id / 32 决定)来使能中断。整个流程保证了中断使能同时兼顾 CPU 映射和硬件寄存器配置。
cs
/*
使能由参数 Int_Id 提供的中断源。调用此函数后,指定 Int_Id 的任何挂起中断条件将被触发。
此 API 还将中断映射到请求的 CPU。
@param InstancePtr 是指向 XScuGic 实例的指针。
@param Int_Id 包含中断源的 ID,应在 0 到 XSCUGIC_MAX_NUM_INTR_INPUTS - 1 的范围内
@return 无。
@note 无。
*/
void XScuGic_Enable(XScuGic InstancePtr, u32 Int_Id)
{
u32 Mask; / 用于保存计算出的位掩码 /
u8 Cpu_Id = (u8)CpuId; / 获取当前 CPU 的 ID */
#if defined (GICv3)
u32 Temp; /* GICv3 下用于读取/写入 redistributor 寄存器的临时变量 */
#endif
/*
* 断言参数
*/
Xil_AssertVoid(InstancePtr != NULL); /* 确保实例指针非空 */
Xil_AssertVoid(Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS); /* 确保中断 ID 在有效范围内 */
Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY); /* 确保驱动已就绪 */
#if defined (GICv3)
if (Int_Id < XSCUGIC_SPI_INT_ID_START) {
/* 对于 SGI/PPI 中断(私有中断),需要操作 redistributor /
XScuGic_InterruptMaptoCpu(InstancePtr, Cpu_Id, Int_Id);
/ 将中断映射到当前 CPU */
Int_Id &= 0x1f; /* 保留低 5 位,因为 SGI/PPI 只有 32 个(0~31) */
Int_Id = 1 << Int_Id; /* 生成对应的位掩码 */
Temp = XScuGic_ReDistSGIPPIReadReg(InstancePtr, XSCUGIC_RDIST_ISENABLE_OFFSET);
/* 读取 redistributor 的 ISENABLE 寄存器 */
Temp |= Int_Id; /* 设置对应位 */
XScuGic_ReDistSGIPPIWriteReg(InstancePtr, XSCUGIC_RDIST_ISENABLE_OFFSET, Temp);
/* 写回,使能该 SGI/PPI 中断 */
}
#endif
XScuGic_InterruptMaptoCpu(InstancePtr, Cpu_Id, Int_Id);
/* 将所有中断(包括 SPI)映射到当前 CPU */
/*
* Int_Id 用于为所需位位置创建适当的掩码。
*/
Mask = 0x00000001U << (Int_Id % 32U);
/* 根据 Int_Id 在 32 位寄存器中的位偏移生成掩码 */
/*
* 通过设置 Enable Set 寄存器中的相应位来使能选定的中断源。
*/
XScuGic_DistWriteReg(InstancePtr, (u32)XSCUGIC_ENABLE_SET_OFFSET +
((Int_Id / 32U) * 4U), Mask);
/* 写入 distributor 的 Enable Set 寄存器,使能中断 */
}
10.Xil_ExceptionRegisterHandler
函数概述: Xil_ExceptionRegisterHandler 用于为特定的异常类型注册处理函数。当处理器遇到指定的异常时,该处理函数会被调用。函数接收异常 ID、处理函数指针以及一个数据引用(Data),将处理函数和数据保存到全局异常向量表 XExc_VectorTable 中。对于 Versal 平台上的 Cortex-A72 处理器(非 R5,运行在 EL3),如果注册的是 IRQ 异常(XIL_EXCEPTION_ID_IRQ_INT),由于 GIC-500 在 EL3 仅支持 FIQ,函数会自动将异常 ID 转换为 FIQ 异常(XIL_EXCEPTION_ID_FIQ_INT),以保证中断处理正常工作。
详细分析: 函数首先进入条件编译块,仅在 versal(Versal 平台)且未定义 ARMR5(即非 Cortex-R5 核心)且定义了 EL3(处理器运行在 EL3 异常级别)时生效。在该条件下,如果传入的 Exception_id 等于 IRQ 中断 ID,则将其替换为 FIQ 中断 ID。这是因为在 Versal 平台上,Cortex-A72 处理器运行在 EL3 时,GIC-500 只支持 FIQ 而不支持 IRQ。随后,函数直接以 Exception_id 为索引访问全局异常向量表 XExc_VectorTable,将对应的 Handler 和 Data 字段分别设置为传入的处理函数指针和数据指针。该函数没有参数有效性断言,调用者需确保 Exception_id 在合法范围内(0 到 XIL_EXCEPTION_ID_LAST),否则会导致越界访问。整个操作仅更新软件数据结构,不涉及硬件配置,执行快速。注册完成后,当处理器遭遇对应异常时,CPU 异常处理入口会查找向量表并调用指定的处理函数,同时将 Data 作为参数传入。
cpp
/*
为特定异常注册处理函数。当处理器遇到指定异常时,该处理函数会被调用。
@param exception_id 包含异常源的 ID,应在 0 到 XIL_EXCEPTION_ID_LAST 范围内。
更多信息参见 xil_exception.h。
@param Handler 该异常的处理函数。
@param Data 是传递给处理函数的数据引用,当处理函数被调用时传入。
@return 无。
@note 无。
*/
void Xil_ExceptionRegisterHandler(u32 Exception_id,
Xil_ExceptionHandler Handler,
void Data)
{
#if defined (versal) && !defined(ARMR5) && EL3
/ 仅当满足以下条件时编译此段:平台为 Versal、非 Cortex-R5 核心、且运行在 EL3 /
if ( XIL_EXCEPTION_ID_IRQ_INT == Exception_id )
{
/
* Versal 平台中的 Cortex-A72 处理器与 GIC-500 配合使用,
* 而 GIC-500 在 EL3 仅支持 FIQ。因此调整此 API,
* 如果 Exception_id 指向 IRQ,则将其改为 FIQ。
*/
Exception_id = XIL_EXCEPTION_ID_FIQ_INT;
}
#endif
XExc_VectorTable[Exception_id].Handler = Handler;
/* 将处理函数指针写入异常向量表的对应条目 */
XExc_VectorTable[Exception_id].Data = Data;
/* 将数据引用写入异常向量表的对应条目 */
}
11.Xil_ExceptionEnableMask
函数概述:Xil_ExceptionEnableMask(Mask) 是一个用于使能指定异常类型的宏。它通过修改 ARM 处理器的 CPSR(Current Program Status Register)寄存器中的异常屏蔽位来实现:将 Mask 参数中指定的异常所对应的屏蔽位清零,从而允许处理器响应这些异常。该宏通常在初始化异常处理后调用,用于逐步开启所需的异常(如 IRQ、FIQ 等)。宏不返回值,且要求处理器处于特权模式,因为 CPSR 的异常屏蔽位仅在特权模式下可写。
详细分析:宏体首先声明一个寄存器变量 Reg,并通过编译器扩展将其绑定到 CPSR 寄存器(register u32 Reg __asm("cpsr")),因此读取 Reg 实际上就是读取当前 CPSR 的值。接着执行 mtcpsr((Reg) & (~((Mask) & XIL_EXCEPTION_ALL))),其中 mtcpsr 是一个用于写 CPSR 的内联汇编或宏。计算过程为:先取 Mask 与 XIL_EXCEPTION_ALL 的按位与,得到本次要操作的有效屏蔽位;然后取反,得到需要清零的位模式;最后与当前 CPSR 值(Reg)按位与,将对应的异常屏蔽位清零,保持其他位不变。在 ARM 架构中,CPSR 的 I、F、A 位为 1 时表示对应的 IRQ、FIQ、异步中止被屏蔽,清零则使能这些异常。因此该宏实现了按需使能异常的功能。由于直接操作 CPSR,宏的执行非常高效,但需要注意:若 Mask 中包含了 XIL_EXCEPTION_ALL 之外的位,这些位不会影响最终结果,因为已被与操作过滤掉。整个宏设计为在特权模式(如 SVC、IRQ 等)下使用,用户需确保操作合法性。
cpp
Xil_ExceptionEnableMask(Mask) \
{ \
register u32 Reg __asm("cpsr"); \
/* 声明一个绑定到 CPSR 寄存器的变量,读取 Reg 即读取当前程序状态寄存器 */
mtcpsr((Reg) & (~((Mask) & XIL_EXCEPTION_ALL))); \
/* 计算新的 CPSR 值:清除 Mask 中指定异常所对应的屏蔽位,
然后写回 CPSR,从而使能这些异常 */
}
11.FindEepromDevice
函数概述 :FindEepromDevice 是一个静态函数,用于检测指定 I2C 地址上是否存在 EEPROM 从设备。它利用 IIC 控制器的从设备监视(Slave Monitor)功能,通过硬件自动检测从设备是否响应,并依赖中断处理程序在后台设置全局标志 SlaveResponse。函数会等待该标志被置位或超时,如果在规定循环次数内检测到响应则返回 XST_SUCCESS,否则返回 XST_FAILURE。该函数典型用于 EEPROM 存在性探测。
详细分析 :函数首先将传入的 Iic_devicePtr 赋值给局部指针 IicPtr,并复位全局标志 SlaveResponse 为 FALSE。随后调用 XIicPs_DisableAllInterrupts 禁用 IIC 控制器的所有中断,再调用 XIicPs_EnableSlaveMonitor 启用从设备监视功能并指定目标设备地址。接着清零全局变量 TotalErrorCount,初始化循环索引 Index 为 0。函数进入 while 循环,等待 SlaveResponse 变为非零值,循环上限由 SLV_MON_LOOP_COUNT 决定。每次循环递增 Index,如果检测到 TotalErrorCount 非零,则通过 xil_printf 打印调试信息"Test error unexpected NACK",表示硬件产生了意外的 NACK 中断,通常说明从设备不存在或通信异常。如果在循环内 SlaveResponse 被置位(由中断服务程序完成),则提前退出循环,否则一直循环到 Index 达到上限。循环结束后,根据 Index 是否达到或超过上限判断是否超时:超时则禁用从设备监视并返回 XST_FAILURE;未超时说明成功检测到从设备,禁用从设备监视并返回 XST_SUCCESS。该实现依赖中断系统正常工作,且从设备监视中断能够触发并置位全局标志,否则会在循环中空转直至超时。函数开头的 DisableAllInterrupts 可能只清除常规 IIC 中断使能,而 Slave Monitor 中断仍可产生,具体行为取决于驱动实现。
cpp
static int FindEepromDevice(XIicPs *Iic_devicePtr, u16 device_address)
{
int Index; /* 循环计数器 */
XIicPs *IicPtr = Iic_devicePtr; /* 保存 IIC 实例指针的局部副本 */
SlaveResponse = FALSE; /* 复位全局从设备响应标志 */
XIicPs_DisableAllInterrupts(IicPtr->Config.BaseAddress); /* 禁用 IIC 所有中断 */
XIicPs_EnableSlaveMonitor(IicPtr, device_address); /* 使能从设备监视,指定目标地址 */
TotalErrorCount = 0; /* 清零全局错误计数 */
Index = 0; /* 初始化循环计数为 0 */
/*
* 等待从设备监视中断,中断处理在后台工作。如果中断工作不正常
* 或从设备从不响应,则本函数可能在该循环中锁死。
*/
while ((!SlaveResponse) && (Index < SLV_MON_LOOP_COUNT)) {
Index++; /* 递增循环计数 */
/*
* 忽略任何错误。如果从设备不存在,硬件会产生 NACK 中断。
*/
if (0 != TotalErrorCount) {
xil_printf("Test error unexpected NACK\n"); /* 打印意外的 NACK 错误信息 */
}
}
if (Index >= SLV_MON_LOOP_COUNT) { /* 判断是否超时 */
XIicPs_DisableSlaveMonitor(IicPtr); /* 超时:禁用从设备监视 */
return XST_FAILURE; /* 返回失败 */
}
XIicPs_DisableSlaveMonitor(IicPtr); /* 未超时:禁用从设备监视 */
return XST_SUCCESS; /* 返回成功 */
}
12.XIicPs_EnableSlaveMonitor
函数概述 : XIicPs_EnableSlaveMonitor 用于使能 IIC 控制器的从设备监视模式。该函数在控制寄存器中使能从设备监视功能,并使能从设备就绪中断,随后向指定的从设备地址发起一次地址传输。如果从设备响应地址传输,中断处理函数会通知调用者。此函数通常用于探测特定从设备是否存在,执行后不等待结果,实际检测由中断服务程序或后续轮询完成。
详细分析 : 函数首先通过 Xil_AssertVoid 断言实例指针非空,确保后续操作安全。随后读取实例配置中的基地址存入局部变量 BaseAddr。接着清除传输大小寄存器(XIICPS_TRANS_SIZE_OFFSET),保证传输状态干净。然后读取控制寄存器,设置主模式(XIICPS_CR_MS_MASK)、清除FIFO(XIICPS_CR_CLR_FIFO_MASK)和从监视模式(XIICPS_CR_SLVMON_MASK)位,同时清除读写方向位(XIICPS_CR_RD_WR_MASK),将方向设为写。根据实例的 Is10BitAddr 标志配置地址模式:若为 10 位地址则清除 XIICPS_CR_NEA_MASK 位,否则设置该位。写回控制寄存器后,调用 XIicPs_EnableInterrupts 仅使能从设备就绪中断(XIICPS_IXR_SLV_RDY_MASK),而不使能 NACK 中断,以便只响应从设备正常应答。之后将从设备暂停寄存器(XIICPS_SLV_PAUSE_OFFSET)初始化为 0xF。最后将目标从设备地址写入地址寄存器(XIICPS_ADDR_OFFSET),启动地址传输。整个函数完成了从监视模式的硬件配置,后续由中断或轮询等待从设备响应。
cpp
/* 该函数使能从设备监视模式。
它在控制寄存器中使能从设备监视,并使能从设备就绪中断。然后对从设备执行一次地址传输。如果从设备响应地址传输,中断处理函数会通知调用者。
@param InstancePtr 是指向 XIicPs 实例的指针。
@param SlaveAddr 是我们想要联系的从设备地址。
@return 无。
@note 无。
*/
void XIicPs_EnableSlaveMonitor(XIicPs InstancePtr, u16 SlaveAddr)
{
u32 BaseAddr; / 保存设备基地址 /
u32 ConfigReg; / 保存控制寄存器值 */
Xil_AssertVoid(InstancePtr != NULL); /* 断言实例指针非空 */
BaseAddr = InstancePtr->Config.BaseAddress; /* 获取设备基地址 */
#if defined (XCLOCKING)
if (InstancePtr->IsClkEnabled == 0) { /* 如果时钟未使能 /
Xil_ClockEnable(InstancePtr->Config.RefClk); / 使能参考时钟 /
InstancePtr->IsClkEnabled = 1; / 更新时钟使能标志 */
}
#endif
/* 清除传输大小寄存器 */
XIicPs_WriteReg(BaseAddr, (u32)XIICPS_TRANS_SIZE_OFFSET, 0x0U);
/*
* 在控制寄存器中使能从监视模式。
*/
ConfigReg = XIicPs_ReadReg(BaseAddr, (u32)XIICPS_CR_OFFSET); /* 读取控制寄存器 */
ConfigReg |= (u32)XIICPS_CR_MS_MASK | (u32)XIICPS_CR_CLR_FIFO_MASK |
(u32)XIICPS_CR_SLVMON_MASK; /* 设置主模式、清 FIFO、从监视模式位 */
ConfigReg &= (u32)(~XIICPS_CR_RD_WR_MASK); /* 清除读写位,设为写方向 */
/*
* 检查是否设置了 10 位地址选项。
*/
if (InstancePtr->Is10BitAddr == 1) {
ConfigReg &= (u32)(~XIICPS_CR_NEA_MASK); /* 10 位地址模式:清除 NEA 位 */
} else {
ConfigReg |= (u32)(XIICPS_CR_NEA_MASK); /* 7 位地址模式:设置 NEA 位 */
}
XIicPs_WriteReg(BaseAddr, (u32)XIICPS_CR_OFFSET, ConfigReg); /* 写回控制寄存器 */
/*
* 设置从监视中断标志。
* 不使能 NACK。
*/
XIicPs_EnableInterrupts(BaseAddr, (u32)XIICPS_IXR_SLV_RDY_MASK); /* 仅使能从设备就绪中断 */
/*
* 初始化从监视寄存器。
*/
XIicPs_WriteReg(BaseAddr, (u32)XIICPS_SLV_PAUSE_OFFSET, 0xFU); /* 写暂停寄存器为 0xF */
/*
* 设置从设备地址以启动从地址传输。
*/
XIicPs_WriteReg(BaseAddr, (u32)XIICPS_ADDR_OFFSET, (u32)SlaveAddr); /* 写入从设备地址 */
return;
}
13.XIicPs_DisableSlaveMonitor
函数概述 :XIicPs_DisableSlaveMonitor 用于禁用 IIC 控制器的从设备监视模式,使控制器从从监视模式返回正常模式。函数会先处理控制寄存器中与 10 位地址模式相关的 NEA 位,然后清除从监视控制位并等待硬件确认清除完成,之后恢复 NEA 位原值,最后禁用从设备就绪中断,并在定义了 XCLOCKING 的情况下关闭参考时钟。通常在从设备探测完成或需要恢复正常 IIC 通信时调用。
详细分析 :函数首先通过 Xil_AssertVoid 断言实例指针非空,并读取设备基地址。然后读取控制寄存器并检查 NEA位(XIICPS_CR_NEA_MASK)是否为0。根据IP文档,当10位地址模式使能时NEA位为0,此时控制器无法直接从从监视模式切回正常模式,必须先置位 NEA 位。因此若NEA为0,函数先写控制寄存器将其置1。接着读取控制寄存器,清除 XIICPS_CR_SLVMON_MASK位,将从监视模式关闭。随后进入一个空 while 循环,不断读取控制寄存器并检查 SLVMON 位,直到该位被硬件清零,表示控制器已真正退出从监视模式。之后,如果最初 NEA 位为 0,则再次清除 NEA 位,恢复之前的 10 位地址模式配置。最后调用 XIicPs_DisableInterrupts 禁用从设备就绪中断(XIICPS_IXR_SLV_RDY_MASK),并在 XCLOCKING 条件下若时钟使能标志为 1,则禁用参考时钟并将标志置 0。整个函数确保了从监视模式被安全、完整地关闭,控制器恢复到正常操作状态
cpp
/*
@brief 该函数禁用从设备监视模式。
@param InstancePtr 是指向 XIicPs 实例的指针。
@return 无。
@note 无。
*/
void XIicPs_DisableSlaveMonitor(XIicPs InstancePtr)
{
u32 BaseAddr; / 保存设备基地址 /
u32 ControlReg; / 保存控制寄存器原始值,用于恢复 NEA 位 */
Xil_AssertVoid(InstancePtr != NULL); /* 断言实例指针非空 */
BaseAddr = InstancePtr->Config.BaseAddress; /* 获取设备基地址 */
/*
* 读取控制寄存器,检查 NEA 位。
* 如果使能了 10 位地址模式,根据 IP 文档,必须清除该位
* 才能使控制器从从监视模式进入正常模式。
*/
ControlReg = XIicPs_ReadReg(BaseAddr, XIICPS_CR_OFFSET); /* 读取控制寄存器 */
if (((ControlReg) & (XIICPS_CR_NEA_MASK)) == 0U) {
/* 如果 NEA 位为 0(10 位地址模式使能) */
XIicPs_WriteReg(BaseAddr, XIICPS_CR_OFFSET,
ControlReg | (XIICPS_CR_NEA_MASK));
/* 置位 NEA 位,以便退出从监视模式 */
}
/*
* 清除从监视控制位。
*/
XIicPs_WriteReg(BaseAddr, XIICPS_CR_OFFSET,
XIicPs_ReadReg(BaseAddr, XIICPS_CR_OFFSET)
& (~XIICPS_CR_SLVMON_MASK));
/* 读取控制寄存器,清除 SLVMON 位,写回以禁用从监视模式 */
/*
* 等待从监视控制位被清除。
*/
while ((XIicPs_ReadReg(BaseAddr, XIICPS_CR_OFFSET)
& XIICPS_CR_SLVMON_MASK) != 0U) {
;
}
/* 轮询控制寄存器,直到 SLVMON 位被硬件清零 */
/*
* 如果前面修改过 NEA 位,将控制寄存器中 NEA 位的先前值写回。
*/
if (((ControlReg) & (XIICPS_CR_NEA_MASK)) == 0U) {
XIicPs_WriteReg(BaseAddr, XIICPS_CR_OFFSET,
XIicPs_ReadReg(BaseAddr, XIICPS_CR_OFFSET)
& (~XIICPS_CR_NEA_MASK));
/* 恢复 NEA 位为 0(10 位地址模式) */
}
/*
* 清除从监视中断标志。
*/
XIicPs_DisableInterrupts(BaseAddr, XIICPS_IXR_SLV_RDY_MASK);
/* 禁用从设备就绪中断 */
#if defined (XCLOCKING)
if (InstancePtr->IsClkEnabled == 1) {
Xil_ClockDisable(InstancePtr->Config.RefClk); /* 禁用参考时钟 /
InstancePtr->IsClkEnabled = 0; / 清零时钟使能标志 */
}
#endif
return;
}
14.EepromWriteData
函数概述: EepromWriteData 是一个静态函数,用于将缓冲区中的数据写入 IIC 串行 EEPROM。它调用 XIicPs_MasterSend 启动发送,然后等待发送完成标志 TransmitComplete 被置位(通常由中断处理函数设置),期间如果错误计数 TotalErrorCount 非零则立即返回失败。发送完成后等待总线空闲并延时,以确保 EEPROM 内部编程周期完成,最后返回成功。该函数假设中断系统已正确配置,否则会在等待循环中死锁。
详细分析: 函数首先将全局标志 TransmitComplete 复位为 FALSE。然后调用 XIicPs_MasterSend,传入实例指针、发送缓冲区、字节数和设备地址,启动主发送操作。随后进入 while 循环等待 TransmitComplete 变为 TRUE。循环体内检查 TotalErrorCount,若该全局变量非零,说明发送过程中发生了错误(如 NACK 或总线错误),函数直接返回 XST_FAILURE。如果发送成功完成且没有错误,循环退出。接着调用 XIicPs_BusIsBusy 轮询直到总线空闲,以便进行下一次传输。之后调用 usleep(250000) 延时 250 毫秒(250,000 微秒),给 EEPROM 足够的时间完成内部写操作(EEPROM 写入后需要一定编程时间)。最后返回 XST_SUCCESS。注意:函数未对 ByteCount 进行校验,调用者应确保其不超过 EEPROM 页大小(PAGE_SIZE),否则可能发生页回绕导致数据错误。
cs
/*
该函数将缓冲区中的数据写入 IIC 串行 EEPROM。
@param ByteCount 包含要写入的缓冲区字节数。
@return 如果成功返回 XST_SUCCESS,否则返回 XST_FAILURE。
@note 字节计数不应超过 EEPROM 的页大小(由常量 PAGE_SIZE 标明)。
*/
static int EepromWriteData(XIicPs IicInstance, u16 ByteCount, u8 device_address)
{
TransmitComplete = FALSE; / 复位发送完成标志 */
/*
* 发送数据。
*/
XIicPs_MasterSend(IicInstance, WriteBuffer,
ByteCount, device_address);
/* 调用主发送函数,启动数据发送 */
/*
* 等待整个缓冲区发送完成,让中断处理在后台工作。
* 如果中断工作不正常,本函数可能会在该循环中锁死。
*/
while (TransmitComplete == FALSE) {
/* 循环直到发送完成标志被置位 */
if (0 != TotalErrorCount) {
/* 如果错误计数非零,表示发送过程中出错 */
return XST_FAILURE;
}
}
/*
* 等待总线空闲以启动下一次传输。
*/
while (XIicPs_BusIsBusy(IicInstance));
/* 轮询总线忙标志,直到总线空闲 */
/*
* 等待一段时间以允许编程完成。
*/
usleep(250000); /* 延时 250 毫秒,等待 EEPROM 内部写完成 */
return XST_SUCCESS; /* 返回成功 */
}
15.XIicPs_MasterSend
函数概述 :XIicPs_MasterSend 用于在 IIC 主模式下启动一次中断驱动的数据发送。它首先配置控制器为主发送角色,尝试填充 FIFO 并发送首批数据,然后使能相关中断(NACK、传输完成、仲裁丢失、超时),最后写入从设备地址启动地址传输,后续数据由中断处理程序在后台完成。该函数仅适用于中断驱动的传输,调用后立即返回,发送状态通过中断和全局标志通知。
详细分析: 函数首先通过 XGetPlatform_Info() 获取当前平台信息,用于后续 Zynq 平台的特定处理。接着使用断言检查 InstancePtr、MsgPtr 非空,驱动已就绪,且 SlaveAddr 未超出地址掩码范围。然后从实例配置中读取基地址,将发送缓冲区指针、字节数和发送标志分别保存到实例的 SendBufferPtr、SendByteCount 和 IsSend 字段,同时将接收缓冲区指针置为 NULL。在 XCLOCKING 条件下,若时钟未使能则使能参考时钟。接下来判断是否需要设置重复启动(HOLD)位:如果实例的 IsRepeatedStart 标志非零,或者待发送字节数大于 FIFO 深度,则置位控制寄存器中的 XIICPS_CR_HOLD_MASK 位,以便在发送完 FIFO 数据后保持总线控制权,继续发送剩余数据。然后调用 XIicPs_SetupMaster 将控制器配置为主发送角色,并调用 TransmitFifoFill 填充 FIFO。在启动中断前,清除中断状态寄存器以确保干净的监控状态,然后使能 NACK、完成、仲裁丢失和超时中断。最后写入从设备地址,触发地址传输。对于 Zynq 平台,如果发送字节数小于 FIFO 深度,则清除 HOLD 位,以确保能正确产生传输完成中断。整个函数只负责初始化传输,实际传输过程由中断驱动完成。
cpp
/* 该函数启动主模式下的中断驱动发送。
它尝试发送首批 FIFO 满载数据,然后让中断处理程序处理剩余数据(如果有的话)。
@param InstancePtr 是指向 XIicPs 实例的指针。
@param MsgPtr 是指向发送缓冲区的指针。
@param ByteCount 是要发送的字节数。
@param SlaveAddr 是我们要发送到的从设备地址。
@return 无。
@note 此发送例程仅用于中断驱动的传输。
*/
void XIicPs_MasterSend(XIicPs *InstancePtr, u8 MsgPtr, s32 ByteCount,
u16 SlaveAddr)
{
u32 BaseAddr; / 保存设备基地址 /
u32 Platform = XGetPlatform_Info(); / 获取当前平台信息 */
/*
* 断言验证输入参数。
*/
Xil_AssertVoid(InstancePtr != NULL); /* 确保实例指针非空 */
Xil_AssertVoid(MsgPtr != NULL); /* 确保发送缓冲区指针非空 */
Xil_AssertVoid(InstancePtr->IsReady == (u32)XIL_COMPONENT_IS_READY); /* 确保驱动已就绪 */
Xil_AssertVoid((u16)XIICPS_ADDR_MASK >= SlaveAddr); /* 确保从地址在有效范围内 */
BaseAddr = InstancePtr->Config.BaseAddress; /* 获取设备基地址 */
InstancePtr->SendBufferPtr = MsgPtr; /* 保存发送缓冲区指针 */
InstancePtr->SendByteCount = ByteCount; /* 保存发送字节数 */
InstancePtr->RecvBufferPtr = NULL; /* 接收缓冲区置空 */
InstancePtr->IsSend = 1; /* 标记当前为发送操作 */
#if defined (XCLOCKING)
if (InstancePtr->IsClkEnabled == 0) { /* 如果时钟未使能 /
Xil_ClockEnable(InstancePtr->Config.RefClk); / 使能参考时钟 /
InstancePtr->IsClkEnabled = 1; / 更新时钟使能标志 */
}
#endif
/*
* 如果发送的数据超过 FIFO 深度,则设置重复启动。
*/
if (((InstancePtr->IsRepeatedStart) != 0) ||
(ByteCount > XIICPS_FIFO_DEPTH)) {
/* 若实例要求重复启动或数据量超过 FIFO 深度 */
XIicPs_WriteReg(BaseAddr, (u32)XIICPS_CR_OFFSET,
XIicPs_ReadReg(BaseAddr, (u32)XIICPS_CR_OFFSET) |
(u32)XIICPS_CR_HOLD_MASK);
/* 置位控制寄存器的 HOLD 位,保持总线控制权 */
}
/*
* 配置为主发送角色。
*/
(void)XIicPs_SetupMaster(InstancePtr, SENDING_ROLE); /* 设置主模式为发送 */
(void)TransmitFifoFill(InstancePtr); /* 填充发送 FIFO */
/*
* 在使用中断状态寄存器监控之前,先清除它。
*/
XIicPs_WriteReg(BaseAddr, XIICPS_ISR_OFFSET, XIICPS_IXR_ALL_INTR_MASK);
/* 清除所有中断状态 */
XIicPs_EnableInterrupts(BaseAddr,
(u32)XIICPS_IXR_NACK_MASK | (u32)XIICPS_IXR_COMP_MASK |
(u32)XIICPS_IXR_ARB_LOST_MASK | (u32)XIICPS_IXR_TO_MASK);
/* 使能 NACK、传输完成、仲裁丢失和超时中断 */
/*
* 执行地址传输以通知从设备。
*/
XIicPs_WriteReg(BaseAddr, XIICPS_ADDR_OFFSET, (u32)SlaveAddr);
/* 写入从设备地址,启动传输 */
/* 如果接收字节数小于 FIFO 深度,在 ZYNQ 上清除 HOLD 位,
* 以便正确产生完成中断。
*/
if ((ByteCount < XIICPS_FIFO_DEPTH) && (Platform == (u32)XPLAT_ZYNQ))
{
XIicPs_WriteReg(BaseAddr, XIICPS_CR_OFFSET,
XIicPs_ReadReg(BaseAddr, (u32)XIICPS_CR_OFFSET) &
(u32)(~XIICPS_CR_HOLD_MASK));
/* 清除 HOLD 位,允许传输完成中断触发 */
}
}
16.XIicPs_SetupMaster
函数概述 :XIicPs_SetupMaster 用于将 IIC 控制器配置为主模式,并设定其角色为发送或接收。函数首先检查总线状态(除非已设置了重复启动保持位),然后设置控制寄存器中的主模式、应答使能、7 位地址模式和 FIFO 清除位,并根据角色设置读写方向位,最后禁用所有中断。该函数通常在启动主传输前调用,为后续的地址传输做准备。
详细分析 :函数首先从实例配置中读取基地址,并读取控制寄存器当前值。若控制寄存器中的 HOLD 位未置位(即不是重复启动的延续),则调用 XIicPs_BusIsBusy 检查总线是否忙,忙则返回 XST_FAILURE。接着,函数对控制寄存器进行置位操作:设置 ACKEN(应答使能)、CLR_FIFO(清除 FIFO)、NEA(7 位地址模式)和 MS(主模式)位。根据传入的 Role 参数,若为接收角色则置位读写位(RD_WR),否则清除该位表示发送。写回控制寄存器后,调用 XIicPs_DisableAllInterrupts 禁用所有中断。该函数不使能中断,调用者若需中断驱动传输,应在调用此函数后自行使能所需中断。注意:函数在设置主模式前仅当 HOLD 位未设置时才检查总线忙,这是为了支持重复启动场景,避免因总线仍被本设备占用而误判失败。
cpp
/*该函数将设备准备为主模式进行传输。
@param InstancePtr 是指向 XIicPs 实例的指针。
@param Role 指定设备是发送还是接收。
@return
XST_SUCCESS 如果一切正常。
XST_FAILURE 如果总线忙。
@note 中断始终被禁用,需要使用中断的设备应在调用此函数后自行设置中断。
*/
s32 XIicPs_SetupMaster(XIicPs InstancePtr, s32 Role)
{
u32 ControlReg; / 控制寄存器值 /
u32 BaseAddr; / 设备基地址 */
BaseAddr = InstancePtr->Config.BaseAddress; /* 获取设备基地址 */
ControlReg = XIicPs_ReadReg(BaseAddr, XIICPS_CR_OFFSET); /* 读取控制寄存器当前值 */
/*
* 仅在未设置重复启动选项时检查总线是否忙。
*/
if ((ControlReg & XIICPS_CR_HOLD_MASK) == 0U) {
/* 如果 HOLD 位为 0,即不是重复启动的延续 */
if (XIicPs_BusIsBusy(InstancePtr) == (s32)1) {
/* 检查总线是否忙 */
return (s32)XST_FAILURE;
}
}
/*
* 设置主模式、应答使能、7 位地址模式,并清除 FIFO。
*/
ControlReg |= (u32)XIICPS_CR_ACKEN_MASK | (u32)XIICPS_CR_CLR_FIFO_MASK |
(u32)XIICPS_CR_NEA_MASK | (u32)XIICPS_CR_MS_MASK;
/* 置位 ACKEN、CLR_FIFO、NEA、MS 位 */
if (Role == RECVING_ROLE) {
/* 如果角色为接收 */
ControlReg |= (u32)XIICPS_CR_RD_WR_MASK; /* 设置读方向位 */
} else {
/* 否则为发送 */
ControlReg &= (u32)(~XIICPS_CR_RD_WR_MASK); /* 清除读方向位 */
}
XIicPs_WriteReg(BaseAddr, XIICPS_CR_OFFSET, ControlReg); /* 写回控制寄存器 */
XIicPs_DisableAllInterrupts(BaseAddr); /* 禁用所有中断 */
return (s32)XST_SUCCESS; /* 返回成功 */
}
17.TransmitFifoFill
函数概述: TransmitFifoFill 是 IIC 驱动内部用于向发送 FIFO 填充数据的辅助函数。它根据当前 FIFO 的可用空间和待发送的字节数,决定本次最多可以写入多少字节,然后调用 XIicPs_SendByte 逐字节写入 FIFO。该函数由主模式和从模式共同使用,通常在启动发送前或发送过程中被调用,以尽量填满 FIFO,减少中断次数。函数返回实例中剩余的待发送字节数(但实现中未更新该值,实际上返回的是调用前的 SendByteCount 原始值,可能在后续流程中由调用者处理)。
详细分析: 函数首先计算 FIFO 中的可用字节数。它通过读取传输大小寄存器(XIICPS_TRANS_SIZE_OFFSET)获取当前 FIFO 中已有的字节数,然后用 XIICPS_FIFO_DEPTH 减去该值得到可用空间 AvailBytes。接着比较 InstancePtr->SendByteCount(本次传输还需发送的总字节数)与 AvailBytes,取较小者作为本次要写入 FIFO 的字节数 NumBytesToSend。然后进入 for 循环,循环 NumBytesToSend 次,每次调用 XIicPs_SendByte(InstancePtr) 写入一个字节到 FIFO,同时该函数内部应当会更新 SendByteCount(递减)和发送缓冲区指针。循环结束后返回 InstancePtr->SendByteCount。需要注意的是,虽然函数注释说返回剩余字节数,在典型的 Xilinx IIC 驱动实现中,XIicPs_SendByte 会从 SendBufferPtr 取一个字节写入 FIFO,并递减 SendByteCount,因此该返回值表示剩余未发送的字节数。该函数设计简洁,每次调用尽可能填充 FIFO,以提高传输效率。
cpp
/*将更多数据放入发送 FIFO,字节数是本次传输期望的字节数或 FIFO 中的可用空间,取两者中较小者。
@param InstancePtr 是指向 XIicPs 实例的指针。
@return 该实例剩余的字节数。
@note 此函数由主设备和从设备共享。
*/
s32 TransmitFifoFill(XIicPs InstancePtr)
{
u8 AvailBytes; / FIFO 中的可用字节数 /
s32 LoopCnt; / 循环计数器 /
s32 NumBytesToSend; / 本次要写入 FIFO 的字节数 */
/*
* 确定要写入 FIFO 的字节数。
*/
AvailBytes = (u8)XIICPS_FIFO_DEPTH -
(u8)XIicPs_ReadReg(InstancePtr->Config.BaseAddress,
XIICPS_TRANS_SIZE_OFFSET);
/* 读取传输大小寄存器,用 FIFO 深度减去当前已占用字节数得到可用空间 */
if (InstancePtr->SendByteCount > (s32)AvailBytes) {
/* 如果待发送字节数大于 FIFO 可用空间 */
NumBytesToSend = (s32)AvailBytes; /* 本次最多写入可用空间大小 */
} else {
/* 否则 */
NumBytesToSend = InstancePtr->SendByteCount; /* 写入全部剩余字节 */
}
/*
* 用上述确定的字节数填充 FIFO。
*/
for (LoopCnt = 0; LoopCnt < NumBytesToSend; LoopCnt++) {
/* 循环写入每个字节 */
XIicPs_SendByte(InstancePtr); /* 发送一个字节到 FIFO */
}
return InstancePtr->SendByteCount; /* 返回剩余待发送字节数 */
}
18.EepromReadData
函数概述: EepromReadData 是一个静态函数,用于从 IIC 串行 EEPROM 的指定地址读取数据到用户缓冲区。函数首先通过一次单字节写操作将 EEPROM 内部地址指针定位到读取起始地址(EEPROM_START_ADDRESS),然后调用 XIicPs_MasterRecv 启动中断驱动的接收过程,等待接收完成标志被置位,期间检查错误计数,最后等待总线空闲。成功返回 XST_SUCCESS,否则返回 XST_FAILURE。该函数适用于中断环境,调用后依赖中断服务程序完成数据接收。
详细分析: 函数首先定义局部变量 Status 保存写地址操作的结果,Address 设为 EEPROM_START_ADDRESS(通常为 0 或其他起始地址),WrBfrOffset 设为 1,表示将写入一个字节。将 Address 的低字节存入全局 WriteBuffer0,然后调用 EepromWriteData(IicInstance, WrBfrOffset, device_address) 发送该地址字节。这一步实际是一个没有数据的写操作,目的是告诉 EEPROM 接下来读取的起始位置。若写地址操作失败(返回非 XST_SUCCESS),函数立即返回 XST_FAILURE。写地址成功后,将全局标志 ReceiveComplete 置为 FALSE,然后调用 XIicPs_MasterRecv(IicInstance, BufferPtr, ByteCount, device_address) 启动主模式接收。该函数配置控制器为接收角色,使能相关中断,并触发传输。随后进入 while 循环等待 ReceiveComplete 变为非零(TRUE),在循环中每次检查全局错误计数 TotalErrorCount,若不为零表示接收过程中发生错误(如 NACK、仲裁丢失、超时等),立即返回失败。接收完成后退出循环,调用 XIicPs_BusIsBusy 等待总线空闲,确保传输完全结束。最后返回 XST_SUCCESS。注意事项:函数没有对 ByteCount 进行范围检查,调用者应确保缓冲区足够大且字节数合理。同时 EEPROM_START_ADDRESS 应与实际 EEPROM 的地址宽度匹配,如果 EEPROM 使用两字节地址,则需要修改 WriteBuffer 填充逻辑。
cpp
/*该函数从 IIC 串行 EEPROM 读取数据到指定的缓冲区。
@param BufferPtr 包含要填充的数据缓冲区的地址。
@param ByteCount 包含要读取的缓冲区字节数。
@return 如果成功返回 XST_SUCCESS,否则返回 XST_FAILURE。
@note 无。
*/
static int EepromReadData(XIicPs *IicInstance, u8 BufferPtr, u16 ByteCount, u16 device_address)
{
int Status; / 保存写地址操作的状态 /
AddressType Address = EEPROM_START_ADDRESS; / 定义读取起始地址 /
int WrBfrOffset; / 写缓冲区偏移,用于指示要写入的字节数 */
/*
* 在 EEPROM 中定位指针。
*/
WriteBuffer[0] = (u8)(Address); /* 将起始地址的低字节放入写缓冲区 */
WrBfrOffset = 1; /* 设置要写入的字节数为 1(仅地址字节) */
Status = EepromWriteData(IicInstance, WrBfrOffset, device_address);
/* 调用 EepromWriteData 发送地址字节,设置 EEPROM 内部地址指针 */
if (Status != XST_SUCCESS) {
/* 如果写地址失败 */
return XST_FAILURE; /* 返回失败 */
}
ReceiveComplete = FALSE; /* 复位接收完成标志 */
/*
* 接收数据。
*/
XIicPs_MasterRecv(IicInstance, BufferPtr,
ByteCount, device_address);
/* 启动主模式接收,将数据读入 BufferPtr 指定的缓冲区 */
while (ReceiveComplete == FALSE) {
/* 等待接收完成标志被置位 */
if (0 != TotalErrorCount) {
/* 如果错误计数非零,表示接收过程中出错 */
return XST_FAILURE;
}
}
/*
* 等待总线空闲以开始下一次传输。
*/
while (XIicPs_BusIsBusy(IicInstance));
/* 轮询总线忙标志,直到总线空闲 */
return XST_SUCCESS; /* 返回成功 */
}
19.XIicPs_MasterRecv
函数概述: XIicPs_MasterRecv 用于在 IIC 主模式下启动一次中断驱动的数据接收。它首先配置控制器为主接收角色,设置传输大小寄存器以告知从设备需要发送的字节数,并使能相关中断(如数据有效、接收溢出、传输完成等),最后写入从设备地址触发地址传输。之后的数据接收由中断处理程序在后台完成,函数立即返回。该函数仅适用于中断驱动的传输。
详细分析: 函数首先通过断言检查 InstancePtr、MsgPtr 非空,驱动已就绪,且 SlaveAddr 在合法地址范围内。然后从实例配置中获取基地址,将接收缓冲区指针和字节数分别保存到实例的 RecvBufferPtr 和 RecvByteCount 字段,同时将发送缓冲区指针置为 NULL,并将 IsSend 标志清零,表示当前为接收操作。接下来判断是否需要设置重复启动(HOLD)位:如果待接收字节数大于 FIFO 深度,或者实例要求重复启动,则置位控制寄存器中的 XIICPS_CR_HOLD_MASK 位,以便在接收过程中保持总线控制权。然后调用 XIicPs_SetupMaster 将控制器配置为主接收角色。之后设置传输大小寄存器:若请求的字节数超过硬件支持的最大传输大小(XIICPS_MAX_TRANSFER_SIZE),则先写入最大传输大小,并记录当前字节数为最大值,同时设置 UpdateTxSize 标志为 1,表示后续需要继续更新传输大小;否则直接写入实际字节数,并将 UpdateTxSize 清零。在启动中断前,清除中断状态寄存器以确保干净的监控状态。接着使能 NACK、数据有效、接收溢出、传输完成、仲裁丢失和超时中断。最后写入从设备地址,触发地址传输。整个函数完成接收初始化,后续数据接收和状态更新由中断服务程序负责。
cpp
/*
该函数启动主模式下的中断驱动接收。
它设置传输大小寄存器,以便从设备可以向我们发送数据。
其余工作由中断处理程序管理。
@param InstancePtr 是指向 XIicPs 实例的指针。
@param MsgPtr 是指向接收缓冲区的指针。
@param ByteCount 是要接收的字节数。
@param SlaveAddr 是我们从哪个从设备接收的地址。
@return 无。
@note 此接收例程仅用于中断驱动的传输。
*/
void XIicPs_MasterRecv(XIicPs *InstancePtr, u8 MsgPtr, s32 ByteCount,
u16 SlaveAddr)
{
u32 BaseAddr; / 保存设备基地址 */
/*
* 断言验证输入参数。
*/
Xil_AssertVoid(InstancePtr != NULL); /* 确保实例指针非空 */
Xil_AssertVoid(MsgPtr != NULL); /* 确保接收缓冲区指针非空 */
Xil_AssertVoid(InstancePtr->IsReady == (u32)XIL_COMPONENT_IS_READY); /* 确保驱动已就绪 */
Xil_AssertVoid((u16)XIICPS_ADDR_MASK >= SlaveAddr); /* 确保从地址在有效范围内 */
BaseAddr = InstancePtr->Config.BaseAddress; /* 获取设备基地址 */
InstancePtr->RecvBufferPtr = MsgPtr; /* 保存接收缓冲区指针 */
InstancePtr->RecvByteCount = ByteCount; /* 保存接收字节数 */
InstancePtr->SendBufferPtr = NULL; /* 发送缓冲区指针置空 */
InstancePtr->IsSend = 0; /* 标记当前为接收操作 */
#if defined (XCLOCKING)
if (InstancePtr->IsClkEnabled == 0) { /* 如果时钟未使能 /
Xil_ClockEnable(InstancePtr->Config.RefClk); / 使能参考时钟 /
InstancePtr->IsClkEnabled = 1; / 更新时钟使能标志 */
}
#endif
if ((ByteCount > XIICPS_FIFO_DEPTH) ||
((InstancePtr->IsRepeatedStart) != 0))
{
/* 如果接收字节数大于 FIFO 深度,或要求重复启动 */
XIicPs_WriteReg(BaseAddr, XIICPS_CR_OFFSET,
XIicPs_ReadReg(BaseAddr, (u32)XIICPS_CR_OFFSET) |
(u32)XIICPS_CR_HOLD_MASK);
/* 置位控制寄存器的 HOLD 位,保持总线控制权 */
}
/*
* 初始化为主接收角色。
*/
(void)XIicPs_SetupMaster(InstancePtr, RECVING_ROLE); /* 设置主模式为接收 */
/*
* 设置传输大小寄存器,以便从设备知道要发送多少数据。
*/
if (ByteCount > (s32)XIICPS_MAX_TRANSFER_SIZE) {
/* 如果请求字节数超过硬件最大传输大小 */
XIicPs_WriteReg(BaseAddr, XIICPS_TRANS_SIZE_OFFSET,
XIICPS_MAX_TRANSFER_SIZE);
/* 写入最大传输大小 */
InstancePtr->CurrByteCount = (s32)XIICPS_MAX_TRANSFER_SIZE;
/* 记录当前传输字节数为最大值 */
InstancePtr->UpdateTxSize = 1;
/* 设置需要更新传输大小的标志 */
} else {
/* 否则 */
InstancePtr->CurrByteCount = ByteCount;
/* 记录当前传输字节数为实际值 */
XIicPs_WriteReg(BaseAddr, (u32)(XIICPS_TRANS_SIZE_OFFSET),
(u32)ByteCount);
/* 将实际字节数写入传输大小寄存器 */
InstancePtr->UpdateTxSize = 0;
/* 清除更新传输大小标志 */
}
/*
* 在使用中断状态寄存器监控之前,先清除它。
*/
XIicPs_WriteReg(BaseAddr, XIICPS_ISR_OFFSET, XIICPS_IXR_ALL_INTR_MASK);
/* 清除所有中断状态 */
XIicPs_EnableInterrupts(BaseAddr,
(u32)XIICPS_IXR_NACK_MASK | (u32)XIICPS_IXR_DATA_MASK |
(u32)XIICPS_IXR_RX_OVR_MASK | (u32)XIICPS_IXR_COMP_MASK |
(u32)XIICPS_IXR_ARB_LOST_MASK | XIICPS_IXR_TO_MASK);
/* 使能 NACK、数据有效、接收溢出、传输完成、仲裁丢失和超时中断 */
/*
* 执行地址传输以通知从设备。
*/
XIicPs_WriteReg(BaseAddr, XIICPS_ADDR_OFFSET, (u32)SlaveAddr);
/* 写入从设备地址,启动接收传输 */
}
20.XIicPs_BusIsBusy
函数概述 : XIicPs_BusIsBusy 用于检查 I2C 总线当前是否处于忙状态。函数通过读取 IIC 控制器的状态寄存器并检查其中的总线活跃位(XIICPS_SR_BA_MASK)来判断总线是否被占用。如果总线忙则返回 TRUE;如果总线空闲,则在定义了 XCLOCKING 的情况下关闭参考时钟并返回 FALSE。该函数通常用于传输启动前或结束后确认总线状态,以避免冲突。
详细分析 : 函数首先使用 Xil_AssertNonvoid 断言实例指针非空,确保后续访问安全。然后读取实例配置中的状态寄存器(偏移 XIICPS_SR_OFFSET),将结果存入局部变量 StatusReg。接着判断 StatusReg 中总线活跃位(XIICPS_SR_BA_MASK)是否非零:若非零,说明总线上有传输正在进行,函数将 Status 置为 TRUE;若为零,表示总线空闲,设置 Status 为 FALSE。最后返回 Status。
cpp
/*
@brief 检查 I2C 总线是否忙
@param InstancePtr 是指向 XIicPs 实例的指针。
@return
TRUE 如果总线忙。
FALSE 如果总线不忙。
@note 无。
*/
s32 XIicPs_BusIsBusy(XIicPs InstancePtr)
{
u32 StatusReg; / 保存状态寄存器值 /
s32 Status; / 保存返回状态 */
/*
* 断言验证输入参数。
*/
Xil_AssertNonvoid(InstancePtr != NULL); /* 确保实例指针非空 */
StatusReg = XIicPs_ReadReg(InstancePtr->Config.BaseAddress,
XIICPS_SR_OFFSET);
/* 读取 IIC 状态寄存器的值 */
if ((StatusReg & XIICPS_SR_BA_MASK) != 0x0U) {
/* 如果总线活跃位被置位,表示总线忙 */
Status = (s32)TRUE;
} else {
/* 总线空闲 */
#if defined (XCLOCKING)
if (InstancePtr->IsClkEnabled == 1) {
/* 如果时钟使能标志为 1 /
Xil_ClockDisable(InstancePtr->Config.RefClk); / 禁用参考时钟 /
InstancePtr->IsClkEnabled = 0; / 清零时钟使能标志 */
}
#endif
Status = (s32)FALSE;
}
return Status; /* 返回总线状态:TRUE 忙,FALSE 空闲 */
}
总结
和其他模块的程序相似,IIC基本的套路也是先配置设备,在配置中断,最终进行数据的收发(读写)。
配置设备的方式和其他设备也相似,先通过 LookupConfig 函数查找基地址,再通过 Initialize 进行初始化,此外还需要通过 XIicPs_SetSClk 函数配置 IIC 总线速率。具体来说,XIicPs_LookupConfig 用于搜索 IIC 设备基地址,函数内容很简单,就是根据 ID 在 XIicPs_ConfigTable 数组中找到对应的基地址并返回给调用者。XIicPs_ConfigTable 的值均由 Xilinx 预设,由 Vivado 工具自动生成。后续所有函数都是在这个基地址的基础上加上偏移量来配置该 IIC 设备的寄存器。XIicPs_CfgInitialize 函数用于完成 IIC 设备的初始化,首先要做的和其他设备一样,就是把配置变量 XIicPs_Config 中的数据(主要是基地址)赋值给 XIicPs 变量。XIicPs 集合了 IIC 设备所有的要素,后续所有操作都以 XIicPs 变量为纽带进行。完成赋值后开始调用 XIicPs_Reset 函数,这个函数从名字上看是复位 IIC 设备,实际上该函数进行了真正的初始化,也就是配置 IIC 的各种寄存器。具体来说,向控制寄存器写入复位默认值 XIICPS_CR_RESET_VALUE,使控制器回到初始状态(包括:从模式、10 位地址、写方向、无 ACK、无 HOLD、无 SLVMON、分频器为 0,即最大 SCL 频率);向超时寄存器写入复位值 0xFF;最后向中断禁用寄存器写入全 1,禁用所有中断。设置完成后调用 XIicPs_GetOptions 函数读取 IIC 的主要配置,赋值给 XIicPs 变量。完成初始化之后,使用 XIicPs_SetSClk 函数配置速率,需要多少 Hz 直接作为实参传递即可,计算方式在前面的博客中已有介绍,函数的详细解释可参考上面的相关内容。至此,IIC 设备的所有配置全部完成。
由于本例程采用中断方式进行数据读写,所以需要配置中断控制器和中断函数。这部分和其他设备也很相似。配置中断控制器还是那两步,分别处理异常和中断。XScuGic_LookupConfig找基地址,XScuGic_CfgInitialize初始化。XScuGic_CfgInitialize干的两件事一个是将配置变量赋值给XScuGic设备变量。然后设置各个中断的桩函数(默认函数,空函数)。这两步可以认为是套路,不管三七二十一,用中断就写上就完事。XScuGic_Enable使能中断控制器。Xil_ExceptionInit初始化异常(zynq里其实什么都没做,空函数),
中断控制器配置完之后开始链接中断服务函数。这里是重点要改的地方。在这里回顾一下中断调用的过程。对于IRQ中断,中断产生之后,CPU接受到此异常,硬件自动到向量表0x18位置执行程序。0x18出只有一句话即 B IRQHandler,CPU执行此指令跳转到 IRQHandler处。在IRQHandler处,CPU将当前环境入栈暂存,然后执行长跳转到IRQInterrupt函数,IRQInterrupt功能是主要是执行XExc_VectorTableEntry 数据中元素5的的函数。这个函数具体是什么需要用户来注册,函数注册通过Xil_ExceptionRegisterHandler实现。一般直接注册为系统标准处理函数XScuGic_InterruptHandler。XScuGic_InterruptHandler的主要功能是InterruptID 调用InstancePtr对应位置的函数和参数。InstancePtr变量中的函数是什么也需要用户来指定,这里通过XScuGic_Connect函数实现。到这里就执行到设备级了,如果需要继续细分,每个设备不一定都相同,一般都是用SetHandler之类的函数。
总结下来,IRQ设备中断注册一般可以分为三步。第一步注册IRQ级别回调函数,这里用Xil_ExceptionRegisterHandler实现,此函数用来处理所有IRQ中断(比如UART,IIC都是IRQ中断,所有IRQ中断都会调用这个此函数)。第二步注册设备级中断,每个设备都有不同的中断ID,进入IRQ中断后会根据ID区分是哪个设备(具体到那个UART,IIC等设备)引起的中断,这里用XScuGic_Connect函数实现。第三步设置不一定是必须,根据设备类型不同可能会有不同,比如UART和IIC有系统标准的中断服务函数做前置处理,用户自己的函数就要通过标准函数做完前置处理后调回,此种情况下需要第三步注册。但像timer这种简单设备,无需前置处理则直接调用用户自定义程序即可。
同前面的分析相同,IIC设备中断函数注册需要将三级函数全部注册。分别用Xil_ExceptionRegisterHandler注册系统IRQ级中断(响应所有IRQ设备),XScuGic_Connect注册IIC设备中断(只响应此IIC设备的中断,进行前置处理),XIicPs_SetStatusHandler注册用户自定义中断。
