前言
明确设计思路,精准定位问题,对于我们后期理解迭代工程有很大的帮助。
这就是我们常说的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