前言
明确设计思路,精准定位问题,对于我们后期理解迭代工程有很大的帮助。
这就是我们常说的40%设计,20%编写和剩下的40%时间进行调试优化。
今天为大家带来的是Zynq-PS端的IIC使用demo,通过驱动外设DS1337来强化对IIC的使用方法。
问题
Q1:PS端的IIC操作和PL端有何不同?
Q2:XIicPs_MasterSendPolled函数的第三个参数为何赋值ByteCount + 1,是否有影响?
Q3:对比PL端的从机地址,为何XIicPs_MasterSendPolled的第四个参数7bit对应不上?
IIC Controller
PL IIC Logic
在了解Zynq IIC总线前,我们先回顾一下PL端在对IIC总线进行逻辑操作时的执行步骤:

【PL IIC Logic】:需要说明的是具体的时序由外设IIC时序确定,比如有的外设支持高低两字节存储地址;应答ACK为低电平,未应答NACK为高电平;PL端通过编写代码逻辑实现通信功能
PS IIC Logic
IIC示例有Polled模式和中断两种模式进行逻辑操作,不同于PL端需要编写相应的逻辑代码驱动IIC外设,PS端需要了解对应的寄存器含义,通过封装好的IIC函数进行寄存器配置即可。

Zynq的PS自带两个IIC控制器,IIC的IO可以映射到MIO或EMIO上。Zynq的IIC控制器支持Master模式或者Slave模式,其中Slave接口通信为APB总线。
本次示例使用的Polled、Master模式,不涉及中断模式相关的寄存器配置说明,感兴趣的伙伴可以前往UG585进行学习,接下来是一些主要寄存器的介绍;
| Module Name | IIC Controller | 
|---|---|
| Software Name | XIICPS | 
| Base Address | 0xE0004000 IIC0 0xE0005000 IIC1 | 
| Description | Inter Integrated Circuit (IIC) | 
| Vendor Info | Cadence IIC | 
| Register Name | Address | Width | Type | Reset Value | Description | 
|---|---|---|---|---|---|
| XIICPS_CR_OFFSET | 0x00000000 | 16 | mixed | 0x00000000 | Control Register | 
| XIICPS_SR_OFFSET | 0x00000004 | 16 | ro | 0x00000000 | Status register | 
| XIICPS_ADDR_OFFSET | 0x00000008 | 16 | mixed | 0x00000000 | IIC Address register | 
| XIICPS_DATA_OFFSET | 0x0000000C | 16 | mixed | 0x00000000 | IIC data register | 
| XIICPS_TRANS_SIZE_OFFSET | 0x00000014 | 8 | rw | 0x00000000 | Transfer Size Register | 
Register XIICPS_CR_OFFSET
| Field Name | Bits | Type | Reset Value | Description | 
|---|---|---|---|---|
| XIICPS_CR_DIV_A_MASK (DIV_A) | 15:14 | rw | 0x0 | Divisor for stage A clock divider. 0 - 3: Divides the input pclk frequency by divisor_a + 1. | 
| XIICPS_CR_DIV_B_MASK (DIV_B) | 13:8 | rw | 0x0 | Divisor for stage B clock divider. 0 - 63 : Divides the output frequency from divisor_a by divisor_b + 1. | 
| reserved | 7 | ro | 0x0 | Reserved, read as zero, ignored on write. | 
| XIICPS_CR_CLR_FIFO_MASK (CLR_FIFO) | 6 | rw | 0x0 | 1 - initializes the FIFO to all zeros and clears the transfer size register except in master receive mode. Automatically gets cleared on the next APB clock after being set. | 
| XIICPS_CR_SLVMON_MASK (SLVMON) | 5 | rw | 0x0 | Slave monitor mode 1 - monitor mode. 0 - normal operation. | 
| XIICPS_CR_HOLD_MASK (HOLD) | 4 | rw | 0x0 | hold_bus 1 - when no more data is available for transmit or no more data can be received, hold the sclk line low until serviced by the host. 0 - allow the transfer to terminate as soon as all the data has been transmitted or received. | 
| XIICPS_CR_ACKEN_MASK (ACKEN) | 3 | rw | 0x0 | This bit needs to be set to 1 1 - acknowledge enabled, ACK transmitted 0 - acknowledge disabled, NACK transmitted. | 
| XIICPS_CR_NEA_MASK (NEA) | 2 | rw | 0x0 | Addressing mode: This bit is used in master mode only. 1 - normal (7-bit) address 0 - reserved | 
| XIICPS_CR_MS_MASK (MS) | 1 | rw | 0x0 | Overall interface mode: 1 - master 0 - slave | 
| XIICPS_CR_RD_WR_MASK (RD_WR) | 0 | rw | 0x0 | Direction of transfer: This bit is used in master mode only. 1 - master receiver 0 - master transmitter. | 
通过上述寄存器字段可以看出主要是关于IIC总线通信状态的一些配置,接下来我们将针对DIV分频和HOLD拉伸字段进行解释;
XIICPS_CR_DIV_MASK
在Master模式下,时钟启用用于建立生成期望的SCL频率

 
      
       
        
        
          I 
         
        
          I 
         
        
          C 
         
        
            
         
        
          S 
         
        
          C 
         
        
          L 
         
        
            
         
        
          C 
         
        
          l 
         
        
          o 
         
        
          c 
         
        
          k 
         
        
          = 
         
        
          C 
         
        
          P 
         
        
          U 
         
         
          
         
           ‾ 
          
         
        
          1 
         
        
          X 
         
         
          
         
           ‾ 
          
         
        
          C 
         
        
          l 
         
        
          o 
         
        
          c 
         
        
          k 
         
        
            
         
        
          / 
         
        
            
         
        
          ( 
         
        
          22 
         
        
          × 
         
        
          ( 
         
        
          d 
         
        
          i 
         
        
          v 
         
        
          i 
         
        
          s 
         
        
          o 
         
        
          r 
         
         
          
         
           ‾ 
          
         
        
          a 
         
        
          + 
         
        
          1 
         
        
          ) 
         
        
          × 
         
        
          ( 
         
        
          d 
         
        
          i 
         
        
          v 
         
        
          i 
         
        
          s 
         
        
          o 
         
        
          r 
         
         
          
         
           ‾ 
          
         
        
          b 
         
        
          + 
         
        
          1 
         
        
          ) 
         
        
          ) 
         
        
       
         IIC \ SCL \ Clock = CPU\underline{}1X\underline{}Clock \ / \ (22 \times (divisor\underline{}a + 1) \times (divisor\underline{}b + 1)) 
        
       
     IIC SCL Clock=CPU1XClock / (22×(divisora+1)×(divisorb+1))
 以下列出了标准和高速SCL时钟的计算值:
| IIC SCL Clock | CPU_1X_Clock | divisor_a | divisor_b | 
|---|---|---|---|
| 100KHz | 111MHz | 2 | 16 | 
| 400KHz | 111MHz | 0 | 12 | 
| 100KHz | 133MHz | 0 | 60 | 
| 400KHz | 133MHz | 2 | 4 | 
| 100KHz | 166MHz | 3 | 16 | 
XIICPS_CR_HOLD_MASK
时钟拉伸则是slave 在master 释放SCL 后,将SCL 主动拉低并保持,此时要求master 停止在SCL 上产生脉冲以及在SDA 上发送数据,直到slave 释放SCL(SCL 为高电平)。之后,master 便可以继续正常的数据传输了。
 如果系统中存在这种低速slave 并且slave 实现了clock stretching,则master 必须实现为能够处理这种情况,实际上大部分slave 设备中不包含SCL 驱动器的,因此无法拉伸时钟。

Register XIICPS_SR_OFFSET
| Field Name | Bits | Type | Reset Value | Description | 
|---|---|---|---|---|
| reserved | 15:9 | ro | 0x0 | Reserved, read as zero, ignored on write. | 
| XIICPS_SR_BA_MASK (BA) | 8 | ro | 0x0 | Bus Active 1 - ongoing transfer on the I2C bus. | 
| XIICPS_SR_RXOVF_MASK (RXOVF) | 7 | ro | 0x0 | Receiver Overflow 1 - This bit is set whenever FIFO is full and a new byte is received. The new byte is not acknowledged and contents of the FIFO remains unchanged. | 
| XIICPS_SR_TXDV_MASK (TXDV) | 6 | ro | 0x0 | Transmit Data Valid - SW should not use this to determine data completion, it is the RAW value on the interface. Please use COMP in the ISR. 1 - still a byte of data to be transmitted by the interface. | 
| XIICPS_SR_RXDV_MASK (RXDV) | 5 | ro | 0x0 | Receiver Data Valid 1 -valid, new data to be read from the interface. | 
| reserved | 4 | ro | 0x0 | Reserved, read as zero, ignored on write. | 
| XIICPS_SR_RXRW_MASK (RXRW) | 3 | ro | 0x0 | RX read_write 1 - mode of the transmission received from a master. | 
| reserved | 2:0 | ro | 0x0 | Reserved, read as zero, ignored on write. | 
Register XIICPS_ADDR_OFFSET
| Field Name | Bits | Type | Reset Value | Description | 
|---|---|---|---|---|
| reserved | 15:10 | ro | 0x0 | Reserved, read as zero, ignored on write. | 
| XIICPS_ADDR_MASK (MASK) | 9:0 | rw | 0x0 | Address 0 - 1024: Normal addressing mode uses add[6:0]. Extended addressing mode uses add[9:0]. | 
Register XIICPS_DATA_OFFSET
| Field Name | Bits | Type | Reset Value | Description | 
|---|---|---|---|---|
| reserved | 15:8 | ro | 0x0 | |
| XIICPS_DATA_MASK (MASK) | 7:0 | rw | 0x0 | data 0 -255: When written to, the data register sets data to transmit. When read from, the data register reads the last received byte of data. | 
Register XIICPS_TRANS_SIZE_OFFSET
| Field Name | Bits | Type | Reset Value | Description | 
|---|---|---|---|---|
| XIICPS_TRANS_SIZE_MASK (MASK) | 7:0 | rw | 0x0 | Transfer Size 0-255 | 
该寄存器字段与FIFO传输有所关联

DS1337 Serial Real-Time Clock
DS1337 Basic Description
DS1337 串行实时时钟是一款低功耗时钟/日历,具有两个可编程时钟闹钟和一个可编程方波输出。地址和数据通过 2 线双向总线串行传输。时钟/日历提供秒、分、时、日、日期、月和年信息。对于少于 31 天的月份,月底的日期会自动调整,包括闰年的更正。时钟以 24 小时或 12 小时格式运行,并带有 AM/PM 指示器。
以下是DS1337的典型工作电路:

DS1337 Registers Map
以下是DS1337驱动使用需要配置的寄存器,本示例使用DS1337的时钟功能,只需关注00H-06H寄存器即可;

DS1377读写模式
Data Write: Slave Receiver Mode

Data Read: Slave Transmitter Mode

程序分析
main()
extern XIicPs I2cInst0;
extern DS1337_TIME rtc;
/*
typedef struct {
	u8 second;
	u8 minute;
	u8 hour;
	u8 dayOfWeek;// day of week, 1 = Monday
	u8 dayOfMonth;
	u8 month;
	u16 year;
} DS1337_TIME;
*/
void setup()
{
    DS1337_startClock();		// 写秒数并启动时钟
    DS1337_fillByYMD(2019,5,10);// 赋值类型为DS1337_TIME的结构体rtc字段-年月日:May 10,2019
    DS1337_fillByHMS(11,20,30);	// 赋值类型为DS1337_TIME的结构体rtc字段-时分秒:11:20:30"
    DS1337_fillDayOfWeek(FRI);	// 赋值类型为DS1337_TIME的结构体rtc字段-dayofWeek:Friday
    DS1337_setTime();			// write time to the RTC chip
}
int main(void)
{
    i2cps_init(&I2cInst0,IIC_DEVICE_ID0);	// 初始化IIC
	setup();
	while(1)
	{
    DS1337_getTime();	// 获取当前DS1337的时间
	xil_printf("%d:%d:%d-%d-%d-%d-",rtc.hour,rtc.minute,rtc.second,rtc.month,rtc.dayOfMonth,rtc.year+2000);
	switch (rtc.dayOfWeek)// Friendly printout the weekday
	{
		case MON:
			xil_printf("MON");
		  break;
		case TUE:
			xil_printf("TUE");
		  break;
		case WED:
			xil_printf("WED");
		  break;
		case THU:
			xil_printf("THU");
		  break;
		case FRI:
			xil_printf("FRI");
		  break;
		case SAT:
			xil_printf("SAT");
		  break;
		case SUN:
			xil_printf("SUN");
		  break;
	}
	xil_printf("\r\n");
	sleep(1);
	}
    return 0;
}
DS1337_setTime()
DS1337_TIME rtc;
I2C_ADDR8 *DS1337_WBUF = (void*)0x08000000;
I2C_ADDR8 *DS1337_RBUF = (void*)0x08100000;
/*
typedef struct
{
	u8   reg_addr[1];
	u8   reg_buf [2048];
}I2C_ADDR8;
*/
// 设置DS1337的初始化时间为11:20:30-5-10-2019-FRI
void DS1337_setTime()
{
	DS1337_WBUF->reg_addr[0]=0x00;
	DS1337_WBUF->reg_buf[0]=DS1337_decToBcd(rtc.second);// 0 to bit 7 starts the clock
	DS1337_WBUF->reg_buf[1]=DS1337_decToBcd(rtc.minute);
	DS1337_WBUF->reg_buf[2]=DS1337_decToBcd(rtc.hour);// If you want 12 hour am/pm you need to set bit 6
	DS1337_WBUF->reg_buf[3]=DS1337_decToBcd(rtc.dayOfWeek);
	DS1337_WBUF->reg_buf[4]=DS1337_decToBcd(rtc.dayOfMonth);
	DS1337_WBUF->reg_buf[5]=DS1337_decToBcd(rtc.month);
	DS1337_WBUF->reg_buf[6]=DS1337_decToBcd(rtc.year);
    i2cps_wr8(&I2cInst0,DS1337_WBUF,7,DS1337_I2C_ADDRESS);	// DS1337_I2C_ADDRESS为0x68
}
/*
void i2cps_wr8(XIicPs *I2C_Ptr, I2C_ADDR8 *MsgPtr , u16 byte_cnt ,u16 slave_addr)
{
    XIicPs_MasterSendPolled(I2C_Ptr, (u8 *)MsgPtr, byte_cnt + 1, slave_addr);
    while (XIicPs_BusIsBusy(I2C_Ptr)) ;
}
*/

从main()程序可知,上板调试DS1337_SeTime()函数之前,已经将rtc时分秒、年月日设置为11:20:30-5-10-2019-FRI
通过前面的DS1337读写模式可知该IIC外设器件地址为1101000,最终通过Register XIIPS_ADDR_OFFSET进行器件地址传输,由描述可知对应低7位[6:0],因此i2cps_wr8函数中的第四个参数为0_110_1000即0x68
XIicPs_MasterSendPolled()
/*
typedef struct {
	XIicPs_Config Config;	// < Configuration structure
	u32 IsReady;		// Device is initialized and ready 
	u32 Options;		// Options set in the device 
	u8 *SendBufferPtr;	// Pointer to send buffer 
	u8 *RecvBufferPtr;	// Pointer to recv buffer 
	s32 SendByteCount;	// Number of bytes still expected to send 
	s32 RecvByteCount;	// Number of bytes still expected to receive 
	s32 CurrByteCount;	// No. of bytes expected in current transfer 
	s32 UpdateTxSize;	// If tx size register has to be updated 
	s32 IsSend;		// Whether master is sending or receiving 
	s32 IsRepeatedStart;	// Indicates if user set repeated start 
	s32 Is10BitAddr;	// Indicates if user set 10 bit address 
	XIicPs_IntrHandler StatusHandler;  // Event handler function 
#if defined  (XCLOCKING)
	u32 IsClkEnabled;	// Input clock enabled 
#endif
	void *CallBackRef;	// Callback reference for event handler 
} XIicPs;
*/
s32 XIicPs_MasterSendPolled(XIicPs *InstancePtr, u8 *MsgPtr,
		 s32 ByteCount, u16 SlaveAddr)
{
	u32 IntrStatusReg;
	u32 StatusReg;
	u32 BaseAddr;
	u32 Intrs;
	s32 Status = (s32)XST_FAILURE;
	u32 timeout = 0;
	_Bool Value;
	/*
	 * Assert validates the input arguments.
	 */
	Xil_AssertNonvoid(InstancePtr != NULL);
	Xil_AssertNonvoid(MsgPtr != NULL);
	Xil_AssertNonvoid(InstancePtr->IsReady == (u32)XIL_COMPONENT_IS_READY);
	Xil_AssertNonvoid((u16)XIICPS_ADDR_MASK >= SlaveAddr);
#if defined  (XCLOCKING)	// #if后逻辑未执行,未定义XCLOCKING
	if (InstancePtr->IsClkEnabled == 0) {
		Xil_ClockEnable(InstancePtr->Config.RefClk);
		InstancePtr->IsClkEnabled = 1;
	}
#endif
	BaseAddr = InstancePtr->Config.BaseAddress;    // 赋值已经初始化的InstancePtr中BaseAddress 0xE0004000
	InstancePtr->SendBufferPtr = MsgPtr;	// 赋值DS1337_WBUF指针的地址0x08000000
	InstancePtr->SendByteCount = ByteCount; // 赋值第三个参数byte_cnt + 1即8,这里的+1是因为写入IIC需要确定写入IIC外设寄存器的初始地址,该示例中即DS1337的00H
    // 写入XIICPS_CR_OFFSET-XIICPS_CR_HOLD_MASK字段,Master控制IIC总线
	if (((InstancePtr->IsRepeatedStart) != 0) ||
		(ByteCount > XIICPS_FIFO_DEPTH)) {
		XIicPs_WriteReg(BaseAddr, XIICPS_CR_OFFSET,
				XIicPs_ReadReg(BaseAddr, (u32)XIICPS_CR_OFFSET) |
						(u32)XIICPS_CR_HOLD_MASK);
	}
	(void)XIicPs_SetupMaster(InstancePtr, SENDING_ROLE);	// This function prepares a device to transfers as a master.
	/*
	 * Intrs keeps all the error-related interrupts.
	 */
	Intrs = (u32)XIICPS_IXR_ARB_LOST_MASK | (u32)XIICPS_IXR_TX_OVR_MASK |
		(u32)XIICPS_IXR_NACK_MASK;
	/*
	 * Clear the interrupt status register before use it to monitor.
	 */
	IntrStatusReg = XIicPs_ReadReg(BaseAddr, XIICPS_ISR_OFFSET);
	XIicPs_WriteReg(BaseAddr, XIICPS_ISR_OFFSET, IntrStatusReg);
	/*
	 * Transmit first FIFO full of data.
	 */
	(void)TransmitFifoFill(InstancePtr);
	...... // 后续代码不再展示,都是一些类似的功能函数,感兴趣的伙伴可以查看XIicPs_MasterSendPolled函数进行查看
}
TransmitFifoFill()
s32 TransmitFifoFill(XIicPs *InstancePtr)
{
	u8 AvailBytes;
	s32 LoopCnt;
	s32 NumBytesToSend;
	/*
	 * Determine number of bytes to write to FIFO.
	 */
	AvailBytes = (u8)XIICPS_FIFO_DEPTH -
		(u8)XIicPs_ReadReg(InstancePtr->Config.BaseAddress,
					   XIICPS_TRANS_SIZE_OFFSET);
	
    // 判断将要发送的字节数量是否小于写FIFO可用字节量,可跳转至目录Register XIICPS_TRANS_SIZE_OFFSET框图进行查看
	if (InstancePtr->SendByteCount > (s32)AvailBytes) {
		NumBytesToSend = (s32)AvailBytes;
	} else {
		NumBytesToSend = InstancePtr->SendByteCount;
	}
	/*
	 * Fill FIFO with amount determined above.
	 */
	for (LoopCnt = 0; LoopCnt < NumBytesToSend; LoopCnt++) {
		XIicPs_SendByte(InstancePtr);
	}
	return InstancePtr->SendByteCount;
}
XIicPs_SendByte()
#define XIicPs_SendByte(InstancePtr)					\
{									\
	u8 Data;							\
	Data = *((InstancePtr)->SendBufferPtr);				\
	 XIicPs_Out32((InstancePtr)->Config.BaseAddress			\
			 + (u32)(XIICPS_DATA_OFFSET), 			\
					(u32)(Data));			\
	(InstancePtr)->SendBufferPtr += 1;				\
	(InstancePtr)->SendByteCount -= 1;\
}
由上述代码可知,依次将DS1337_WBUF指针指向的reg_addr和reg_buf填充至FIFO
| *SendBuffPtr | Data -> FIFO | SendByteCount | 
|---|---|---|
| 0x08000000 | 0x00(要写入DS1337的初始寄存器地址) | 8 | 
| 0x08000001 | 0x30(Send) | 7 | 
| 0x08000002 | 0x20(minute) | 6 | 
| 0x08000003 | 0x11(hour) | 5 | 
| 0x08000004 | 0x05(dayOfWeek) | 4 | 
| 0x08000005 | 0x10(dayOfMonth) | 3 | 
| 0x08000006 | 0x05(month) | 2 | 
| 0x08000007 | 0x19(year) | 1 | 
结果展示
经过一系列函数操作后,驱动DS1337并初始化设置为11:20:30-5-10-2019-FRI,后DS1337内部自动计时,PS端通过IIC与DS1337通信,间隔1s读取时间并通过串口打印信息

参考
- Zynq 7000 SoC Technical Reference Manual(UG585)
- 米联客ZynqSocSDK入门篇-IIC-RTC实验
- DS1337 Serial Real-Time Clock



















