ANT无线通信技术(5) ANT与MCU的SPI通信时序分析及相关程序设计
ANT与MCU可以使用异步UART或同步SPI两种方式连接。异步通信与同步通信的各自特点这里不赘述,总之我们选择使用同步方式进行连接。
一、SPI简介
SPI(Serial Peripheral Interface),串行外设接口。是摩托罗拉公司开发的一种同步全双工通信协议。依靠收发两端的移位寄存器,以及主机master提供的时钟信号,双方可以实现较高速率的同步全双工传输。
标准的SPI是3/4根线,分别用于一主一从/多主从的情况。4根线分别是:
- MOSI 主机发,从机收 master out slave in
- MISO 主机收,从机发 master in slave out
- SCLK 主机时钟输出,从机时钟输入
- CS 片选信号,用于多主从情况
有些命名可能有区别,不过意思相同。
二、ANT中的SPI
SPI固然很强大,然而,由于协议中缺少流控,面对后来出现的各种各样的外设,他渐渐开始力不从心了。例如,在一些高速应用中,收发双发需要进行严格的握手确认机制,已保证可靠的数据传输,然而标准SPI协议中,通过仅有的几根数据线,master甚至无法知道slave的存在!再比如,万一MOSI线和MISO线短路到一起了,主机有可能仍然正常工作,而无法发觉从机已挂!这正是我们在调试单机SPI的时候那种做法。因此,现实的情况是,我们所见的很多的SPI设备,其实都并没有使用标准的SPI协议,而是纷纷开发了多种多样的SPI兼容的传输方式。ANT就是这样,借用了SPI“挤牙膏”式的数据传输机制,配上了自己的一套流控方法,最后全双工简化成了半双工。
在连接MCU与ANT时,可以使用硬件SPI接口。借助另外三根信号线SRDY,MRDY,EN,便可以完成与ANT模块的所有通信。当然,对于没有硬件SPI的低成本单片机,也可以使用软件模拟SPI来完成通信。
1.MRDY a Message is ReaDY to ANT
2.EN ANT modual is ENabled to response
3.SRDY MCU is ready to receive message from ANT
4.SCLK same as SPI
5.MOSI from ANT to MCU
6.MISO from MCU to ANT
需要指出的是,虽然在功能上十分类似,但由于一些细小的差异,这里的SRDY不能使用SPI中的CS信号线来代替。所以说,我们使用SPI的3pin从模式就可以了。ANT模块作为SPI主机,MCU做SPI从机。(官方文档中说到EPSON有一个系列的单片机可以用硬件SPI的片选信号直接作SRDY,我猜测很可能就是那款单片机内置了ANT的这种SPI兼容协议)
我的Launchpad是MSP430G2553,恰好具有一个硬件SPI,所以接下来这里我们实现硬件SPI接口的连接。既然是同步通信,自然少不了时序。TIME IS MONEY!
nRF24AP2的SPI通信机制基本描述如下:
-
- 半双工模式的SPI,ANT为主,MCU为从
- MRDY,EN,外加SRDY,共同完成握手以及流控
- nRF24AP2作主节点时,自动转发所有收到的消息至MCU
- MCU使用串口,必须在请求得到nRF24AP2回应后才可以
1.同步
ANT模块上电后,需要与MCU进行一次同步操作,才能正常开始SPI通信。同步可以通过两种方式完成,一种是拉一下ANT上面的RESET-Pin,简单可靠,但需要设计硬件按钮,或者占用一个MCU的接口。另一种就是通过MRDY和SRDY,EN三根线完成一个握手,纯软件实现,成本低廉。下面的图描述了完成一次SPI同步初始化握手的时序图,我们也可以认为这个过程是实现了一个同步序列Synchronous Sequence。
蓝色的箭头标明了数据的流向,下同。我简洁地表述下同步的整个过程:
a) 最初,MCU拉高MRDY,SRDY
b) 首先,MCU拉低SRDY
c) 间隔至少250us,然后MCU拉低MRDY
d) 若SEN为低,等待ANT拉高SEN
e) ANT拉高SEN后会再次拉低SEN,表示同步完成
f) MCU最后拉高SRDY,同步结束
2.MCU接收 (SYNC=0xA4)
通过此过程,ANT得以发送消息至MCU。须知,一个消息的完整传输,第一个字节也就是SYNC字节,总是由ANT发起。SYNC字节(的最低位)标明了后续字节的流向。
简洁描述如下:
a) ANT自动拉低SEN,提示MCU有消息给你
b) MCU发觉后,待自己准备好了,便发送一个SRDY的负脉冲告诉ANT,脉冲持续至少2.5us
c) ANT感知到负脉冲,随后将使能SCLK信号,发送SYNC字节0xA4
d) MCU再次准备好后,便可继续来一发SRDY负脉冲,进行后续字节的通信
3.MCU发送 (SYNC=0xA5)
当MCU有消息要发送给ANT模块的时候,进行这个操作。同样的,第一个SYNC字节仍是来自ANT。正确的SYNC,标明ANT已经准备好接收消息。
简洁描述如下:
a) MCU拉低MRDY,表示自己有消息要给ANT
b) ANT发觉后,拉低SEN回应
c) MCU得到回应,准备完毕便发送一个SRDY负脉冲,至少2.5us
d) ANT听到负脉冲,便发出SCLK信号,SYNC字节0xA5随之而来
e) 确认SYNC字节无误后,MCU再次给出一个SRDY负脉冲,表示自己随时可以发送数据
f)ANT听到负脉冲,便发出SCLK信号,指引MCU发送下一个字节的数据
e) MCU根据SCLK的节奏,随即发出下一个字节
g) 后续操作相同
三、基本程序设计
根据以上过程,可以归纳出大致的程序流程图。
接下来是CODE TIME!
1.先是同步的部分。
1 /////////////////////////////////////////////////////////////////////////////////// 2 //完成同步序列的发送 3 //返回值: IS_EN_ASSERTED() 4 BOOL ByteSyncSerial_SyncSequenceReset(void) 5 { 6 SYNC_SRDY_DEASSERT(); //先拉高SRDY,MRDY 7 SYNC_MRDY_DEASSERT(); 8 Timer_DelayTime(300); //等待ANT初始化,约1ms 9 10 //正式开始同步序列 11 SYNC_SRDY_ASSERT(); //先拉低SRDY 12 13 Timer_DelayTime(10); //等待10*30us=300us》250us 14 15 SYNC_MRDY_ASSERT(); //再拉低MRDY 16 17 Timer_DelayTime(1000); //等待ANT拉低EN响应 18 19 SYNC_SRDY_ASSERT(); //拉低SRDY 20 //同步结束 21 22 return IS_EN_ASSERTED(); //返回EN是否已拉低 23 }
2.然后是接收和发送消息的部分。
1 //////////////////////////////////////////////////////////////////////////////////// 2 //用于接收消息或发送消息,发送的消息用指针引自发送缓存pucTxMsgBuffer,接收的消息用指针保存至接收缓存pucRxMsgBuffer 3 //参数: pucTxMsgBuffer 4 // pucRxMsgBuffer 5 //返回值: bRxMessage 6 BOOL ByteSyncSerial_Transaction(UCHAR *pucTxMsgBuffer, UCHAR *pucRxMsgBuffer) 7 { 8 BOOL bRxMessage = FALSE; //成功接收消息标志 9 UCHAR ucCheckSum; 10 UCHAR ucMesgLen; 11 UCHAR ucIndex; 12 UCHAR ucByte; 13 14 if (pucTxMsgBuffer != NULL) //MCU有消息发给ANT 15 { 16 SYNC_MRDY_ASSERT(); //拉高MRDY通知ANT 17 18 { 19 UCHAR ucEnTimeout = EN_ASSERT_TIMEOUT; 20 21 do //循环等待ANT拉低EN来响应 22 { 23 if (!(ucEnTimeout--)) 24 { 25 ucEnTimeout = EN_ASSERT_TIMEOUT; 26 27 SYNC_MRDY_DEASSERT(); //重试拉低MRDY 28 TIMER_EN_DELAY(); 29 SYNC_MRDY_ASSERT(); 30 } 31 32 TIMER_EN_DELAY(); //等待10us 33 } 34 while (!IS_EN_ASSERTED()); //直到EN拉低 35 } 36 } 37 38 while (IS_MRDY_ASSERTED() || (IS_EN_ASSERTED() && (!bRxMessage))) //MCU有消息至ANT,或者ANT已响应即将有个消息要发过来且还没收到 39 { 40 while (!IS_EN_ASSERTED()); 41 42 ucByte = ReadByte(); //读取SYNC字节 43 ucCheckSum = ucByte; //初始化校验和 44 45 if (ucByte == MESG_RX_SYNC) //若SYNC=0xA5 MCU->ANT 46 { 47 SYNC_MRDY_DEASSERT(); //拉高MRDY信号,MCU有话要说 48 49 ucMesgLen = pucTxMsgBuffer[0] + MESG_SAVED_FRAME_SIZE; //读取待发送信息的长度,并根据DataLength位和ID位+2 50 ucIndex = 0; 51 52 SYNC_SRDY_ASSERT(); //准备好了可以开始写了 53 54 do //循环写完信息(不含校验和),并更新校验和 55 { 56 WriteByte(pucTxMsgBuffer[ucIndex]); 57 ucCheckSum ^= pucTxMsgBuffer[ucIndex]; 58 } 59 while (++ucIndex < ucMesgLen); 60 61 WriteByte(ucCheckSum); //写出校验和 62 } 63 else if (ucByte == MESG_TX_SYNC) //若SYNC=0xA4 MCU<-ANT 64 { 65 SYNC_SRDY_ASSERT(); //确认完毕,准备好接受下一个字节 66 ucByte = ReadByte(); //第二个字节发过来的就是DataLength 67 ucMesgLen = ucByte + MESG_SAVED_FRAME_SIZE; //DataLength+2得到整体长度 68 ucIndex = 0; 69 70 do //循环读完剩下的消息 71 { 72 pucRxMsgBuffer[ucIndex] = ucByte; //存入接收缓存 73 ucCheckSum ^= ucByte; //更新校验和 74 ucByte = ReadByte(); 75 } 76 while (++ucIndex < ucMesgLen); 77 78 if (ucCheckSum == ucByte) //确认校验和无误 79 { 80 bRxMessage = TRUE; //置位成功接收标志 81 SYNC_SRDY_ASSERT(); //处理完毕,等待下一个字节到来 82 } 83 } 84 } 85 86 return bRxMessage; //返回接收信息标志 87 }