Microchip PIC24F单片机如何通过SPI增强缓冲模式正确接收数据?
How to correctly receive data through SPI enhanced buffer mode of Microchip PIC24F microcontrollers?
我正在编写与外部 RF 芯片的 SPI 通信。微控制器是Microchip的型号PIC24FJ64GA102。
我想使用 SPI 的增强缓冲模式。
问题:从接收缓冲区中取出接收到的字节。
使用的SPI函数:
void SPI1_get(uint8_t* data, uint16_t length) {
uint16_t i = 0, l = length;
uint8_t dummy;
while (length > 0) {
while (SPI1STATbits.SPITBF || SPI1STATbits.SPIRBF) {
dummy = SPI1STAT;
}
do {
SPI1BUF = 0xff;
} while (SPI1STATbits.SPIRBF == 0 && --length > 0);
do {
while (SPI1STATbits.SRMPT == 0) {
}
data[i] = SPI1BUF;
++i;
} while (i < l && SPI1STATbits.SRXMPT != 1);
}
}
这里是电话:
uint8_t cmd[2]; cmd[0] = length; cmd[1] = address;
SPI1_put(cmd,2); // e.g: 0x02, 0x01
SPI1_get(buf,2); // e.g: 0x05, 0x01 (received data)
通信正常,用示波器和SPI解码模块检查。 SPI总线上的数据如上面的注释:sent 0x02 0x01 0xff 0xff
, received 0x00 0x00 0x05 0x01
, 但是上面的函数没有正确地从接收缓冲区中取出数据。我已经测试了很多检查标志和中断的星座,但最后我能得到的最好结果是:0x00 0x01
(只有最后一个字节是正确的)。
还检查了勘误表 sheet,其中提到了两个不会(不应)影响我的代码的 SPI 问题。
我到底做错了什么?!
根据此处的要求,SPI1_put() 函数:
void SPI1_put(uint8_t* data, uint16_t length) {
uint16_t i = 0;
uint8_t dummy;
for (; i < length; ++i) {
while (SPI1STATbits.SPITBF)
; // maybe change to (_SPI1BEC == 7) ?
SPI1BUF = data[i];
dummy = SPI1BUF; //dummy read
}
}
[最新编辑:2015-02-05]
所以今天我能够在这个特定问题上多花一些时间,并提出了 ElderBug 的建议,同时解决了勘误表中提到的错误 sheet:
uint8_t out_buf[128];
uint8_t in_buf[128];
void SPI1_com(uint8_t* out, uint8_t* in, uint16_t out_len, uint16_t in_len) {
uint16_t len = out_len + in_len;
uint16_t sent = 0, recv = 0, i = 0;
// while (!SPI1STATbits.SRXMPT)
sent = SPI1BUF; // empty buffer
sent = SPI1BUF; // empty buffer
sent = 0;
if (out != out_buf && out != 0)
memcpy(out_buf, out, out_len);
while (sent < len && recv < len) {
if (SPI1STATbits.SPIBEC != 7 && sent < len) {
SPI1BUF = out_buf[sent++];
}
if (!SPI1STATbits.SRXMPT && recv < len) {
in_buf[recv] = SPI1BUF, recv++;
}
}
if (in != 0) {
for (i = 0; i < in_len; ++i) {
in[i] = in_buf[out_len + i];
}
// memcpy(in, in_buf + out_len, in_len);
}
for (i = 0; i < len; ++i) {
out_buf[i] = 0xff;
in_buf[i] = 0xff;
}
}
此代码基本有效。除了我无法解决的例外情况:
通信工作如下:
- 1字节:r/w-bit+长度
- 1字节:地址
- 1-127字节:数据
所以,当我从从芯片读取 1 个字节时,意味着发送 3 个字节(rw+len、地址、虚拟字节),我 in_buf
缓冲区中的数据是 0xFF。
现在有一件奇怪的事情:当我再读一个字节时,没有改变任何东西(总线上只是一个虚拟字节),我的 in_buf
的第一个字节得到了正确的数据。但这似乎并不总是有效,因为我的程序仍然在某些地方卡住了。
我带着很多问号坐在这里。
奇怪的事情第二个:读取8个字节,我的缓冲区中的数据正确到最后一个字节,最后一个字节是0xFF,应该是0x00。什么鬼?
PS: 已经在 Microchip 提交了此问题支持的工单。
你的SPI1_put
有问题。
在您的 SPI1_put
函数中,您在开始传输后立即阅读 SPI1BUF
,因此您在传输未完成时阅读(如果我错了请纠正我,但当FIFO 为空不会阻塞,只是 return 前一个字节)。该函数应该类似于:
void SPI1_put(uint8_t* data, uint16_t length) {
uint16_t sent = 0;
uint16_t rec = 0;
uint8_t dummy;
while(1)
{
if( !SPI1STATbits.SPITBF && sent < length )
SPI1BUF = data[sent++];
if( !SPI1STATbits.SRXMPT && rec < length )
dummy = SPI1BUF, rec++;
if( sent == length && rec == length )
break;
}
}
关于SPI1_get
,我觉得还可以,但我不确定。问题是,它看起来应该与 SPI1_put
非常相似,因为 SPI 在工作方式上确实是对称的:
void SPI1_get(uint8_t* data, uint16_t length) {
uint16_t sent = 0;
uint16_t rec = 0;
while(1)
{
if( !SPI1STATbits.SPITBF && sent < length )
SPI1BUF = 0xff, sent++;
if( !SPI1STATbits.SRXMPT && rec < length )
data[rec++] = SPI1BUF;
if( sent == length && rec == length )
break;
}
}
总的来说,因为你只是做同步调用,所以增强缓冲区并不是很有用。您可以通过对每个字节执行 'send, wait, receive' 来获得具有相同结果的更简单的函数。
编辑 更新后:
您的新代码看起来是正确的,除了 while
中的条件您做错了。这是更正后的代码,其中包含一些可以改进它的注释的修改(删除了缓冲区副本):
// No additional buffers
void SPI1_com(uint8_t* out, uint8_t* in, uint16_t out_len, uint16_t in_len) {
uint16_t len = out_len + in_len;
uint16_t sent = 0, recv = 0, i;
uint16_t dummy;
// After the call, SPI comms should have ended and RX FIFO should be empty.
// If SPI1STATbits.SRXMPT is not 1 here,
// it means there is a problem in the code. Report it somehow for debug ?
// if( !SPI1STATbits.SRXMPT )
// error!
// This loop is harmless but shouldnt be needed
//while (!SPI1STATbits.SRXMPT)
//dummy = SPI1BUF; // empty buffer
i = 0;
while (sent < len || recv < len) {
if (SPI1STATbits.SPIBEC != 7 && sent < len) {
// Here we are out of the buffer when sent>=out_len,
// but it's ok because random bytes are fine here
SPI1BUF = out[sent++];
}
if (!SPI1STATbits.SRXMPT && recv < len) {
if( recv < out_len )
// Before out_len, discard the data
dummy = SPI1BUF;
else
// After out_len, store the data in in[]
in[i++] = SPI1BUF;
recv++;
}
}
}
这是否纠正了任何错误?另外,这听起来可能很愚蠢,但你说得对吗?我不知道你做了什么,但对于你的 1 字节读取,它应该是 SPI1_com(two_byte_buffer,one_byte_buffer,2,1);
.
还有一点:看来你总是在一开始就尝试清空RX FIFO。 FIFO 里真的有东西吗? SPI1STATbits.SRXMPT
必须为 1,甚至不尝试清空。如果不是,则软件某处存在错误。您还有其他使用 SPI 的代码吗?
我正在编写与外部 RF 芯片的 SPI 通信。微控制器是Microchip的型号PIC24FJ64GA102。
我想使用 SPI 的增强缓冲模式。
问题:从接收缓冲区中取出接收到的字节。
使用的SPI函数:
void SPI1_get(uint8_t* data, uint16_t length) {
uint16_t i = 0, l = length;
uint8_t dummy;
while (length > 0) {
while (SPI1STATbits.SPITBF || SPI1STATbits.SPIRBF) {
dummy = SPI1STAT;
}
do {
SPI1BUF = 0xff;
} while (SPI1STATbits.SPIRBF == 0 && --length > 0);
do {
while (SPI1STATbits.SRMPT == 0) {
}
data[i] = SPI1BUF;
++i;
} while (i < l && SPI1STATbits.SRXMPT != 1);
}
}
这里是电话:
uint8_t cmd[2]; cmd[0] = length; cmd[1] = address;
SPI1_put(cmd,2); // e.g: 0x02, 0x01
SPI1_get(buf,2); // e.g: 0x05, 0x01 (received data)
通信正常,用示波器和SPI解码模块检查。 SPI总线上的数据如上面的注释:sent 0x02 0x01 0xff 0xff
, received 0x00 0x00 0x05 0x01
, 但是上面的函数没有正确地从接收缓冲区中取出数据。我已经测试了很多检查标志和中断的星座,但最后我能得到的最好结果是:0x00 0x01
(只有最后一个字节是正确的)。
还检查了勘误表 sheet,其中提到了两个不会(不应)影响我的代码的 SPI 问题。
我到底做错了什么?!
根据此处的要求,SPI1_put() 函数:
void SPI1_put(uint8_t* data, uint16_t length) {
uint16_t i = 0;
uint8_t dummy;
for (; i < length; ++i) {
while (SPI1STATbits.SPITBF)
; // maybe change to (_SPI1BEC == 7) ?
SPI1BUF = data[i];
dummy = SPI1BUF; //dummy read
}
}
[最新编辑:2015-02-05]
所以今天我能够在这个特定问题上多花一些时间,并提出了 ElderBug 的建议,同时解决了勘误表中提到的错误 sheet:
uint8_t out_buf[128];
uint8_t in_buf[128];
void SPI1_com(uint8_t* out, uint8_t* in, uint16_t out_len, uint16_t in_len) {
uint16_t len = out_len + in_len;
uint16_t sent = 0, recv = 0, i = 0;
// while (!SPI1STATbits.SRXMPT)
sent = SPI1BUF; // empty buffer
sent = SPI1BUF; // empty buffer
sent = 0;
if (out != out_buf && out != 0)
memcpy(out_buf, out, out_len);
while (sent < len && recv < len) {
if (SPI1STATbits.SPIBEC != 7 && sent < len) {
SPI1BUF = out_buf[sent++];
}
if (!SPI1STATbits.SRXMPT && recv < len) {
in_buf[recv] = SPI1BUF, recv++;
}
}
if (in != 0) {
for (i = 0; i < in_len; ++i) {
in[i] = in_buf[out_len + i];
}
// memcpy(in, in_buf + out_len, in_len);
}
for (i = 0; i < len; ++i) {
out_buf[i] = 0xff;
in_buf[i] = 0xff;
}
}
此代码基本有效。除了我无法解决的例外情况:
通信工作如下:
- 1字节:r/w-bit+长度
- 1字节:地址
- 1-127字节:数据
所以,当我从从芯片读取 1 个字节时,意味着发送 3 个字节(rw+len、地址、虚拟字节),我 in_buf
缓冲区中的数据是 0xFF。
现在有一件奇怪的事情:当我再读一个字节时,没有改变任何东西(总线上只是一个虚拟字节),我的 in_buf
的第一个字节得到了正确的数据。但这似乎并不总是有效,因为我的程序仍然在某些地方卡住了。
我带着很多问号坐在这里。
奇怪的事情第二个:读取8个字节,我的缓冲区中的数据正确到最后一个字节,最后一个字节是0xFF,应该是0x00。什么鬼?
PS: 已经在 Microchip 提交了此问题支持的工单。
你的SPI1_put
有问题。
在您的 SPI1_put
函数中,您在开始传输后立即阅读 SPI1BUF
,因此您在传输未完成时阅读(如果我错了请纠正我,但当FIFO 为空不会阻塞,只是 return 前一个字节)。该函数应该类似于:
void SPI1_put(uint8_t* data, uint16_t length) {
uint16_t sent = 0;
uint16_t rec = 0;
uint8_t dummy;
while(1)
{
if( !SPI1STATbits.SPITBF && sent < length )
SPI1BUF = data[sent++];
if( !SPI1STATbits.SRXMPT && rec < length )
dummy = SPI1BUF, rec++;
if( sent == length && rec == length )
break;
}
}
关于SPI1_get
,我觉得还可以,但我不确定。问题是,它看起来应该与 SPI1_put
非常相似,因为 SPI 在工作方式上确实是对称的:
void SPI1_get(uint8_t* data, uint16_t length) {
uint16_t sent = 0;
uint16_t rec = 0;
while(1)
{
if( !SPI1STATbits.SPITBF && sent < length )
SPI1BUF = 0xff, sent++;
if( !SPI1STATbits.SRXMPT && rec < length )
data[rec++] = SPI1BUF;
if( sent == length && rec == length )
break;
}
}
总的来说,因为你只是做同步调用,所以增强缓冲区并不是很有用。您可以通过对每个字节执行 'send, wait, receive' 来获得具有相同结果的更简单的函数。
编辑 更新后:
您的新代码看起来是正确的,除了 while
中的条件您做错了。这是更正后的代码,其中包含一些可以改进它的注释的修改(删除了缓冲区副本):
// No additional buffers
void SPI1_com(uint8_t* out, uint8_t* in, uint16_t out_len, uint16_t in_len) {
uint16_t len = out_len + in_len;
uint16_t sent = 0, recv = 0, i;
uint16_t dummy;
// After the call, SPI comms should have ended and RX FIFO should be empty.
// If SPI1STATbits.SRXMPT is not 1 here,
// it means there is a problem in the code. Report it somehow for debug ?
// if( !SPI1STATbits.SRXMPT )
// error!
// This loop is harmless but shouldnt be needed
//while (!SPI1STATbits.SRXMPT)
//dummy = SPI1BUF; // empty buffer
i = 0;
while (sent < len || recv < len) {
if (SPI1STATbits.SPIBEC != 7 && sent < len) {
// Here we are out of the buffer when sent>=out_len,
// but it's ok because random bytes are fine here
SPI1BUF = out[sent++];
}
if (!SPI1STATbits.SRXMPT && recv < len) {
if( recv < out_len )
// Before out_len, discard the data
dummy = SPI1BUF;
else
// After out_len, store the data in in[]
in[i++] = SPI1BUF;
recv++;
}
}
}
这是否纠正了任何错误?另外,这听起来可能很愚蠢,但你说得对吗?我不知道你做了什么,但对于你的 1 字节读取,它应该是 SPI1_com(two_byte_buffer,one_byte_buffer,2,1);
.
还有一点:看来你总是在一开始就尝试清空RX FIFO。 FIFO 里真的有东西吗? SPI1STATbits.SRXMPT
必须为 1,甚至不尝试清空。如果不是,则软件某处存在错误。您还有其他使用 SPI 的代码吗?