|
发表于 2011-9-10 00:08:50
|
显示全部楼层
解决了就好,下面我贴上的是正式书中的代码例子,与你解决的处理思想类似的,采用了一个位变量:SPI_free,而不使用SPSR中的状态标志位,
===============================================================================
15.3.2典型SPI底层驱动+中间层软件结构示例
同样我们也可以采用底层中断驱动+中间层的结构,把SPI接口部分相对的独立出来。下面一段是SPI主机方式连续发送(接收)字节的例程:
#define SIZE 100
unsigned char SPI_rx_buff[SIZE];
unsigned char SPI_tx_buff[SIZE];
unsigned char rx_wr_index,rx_rd_index,rx_counter,rx_buffer_overflow;
unsigned char tx_wr_index,tx_rd_index,tx_counter;
unsigned char SPI_free;
interrupt [SPI_STC] void spi_isr(void) // SPI 完成中断服务
{
SPI_rx_buff[rx_wr_index] = SPDR; // 从SPI口读出收到的字节放入接收缓冲区
if (tx_counter) // 如果发送缓冲区中有待发的数据
{
SPDR = SPI_tx_buff[tx_rd_index]; // 发送1字节数据,
--tx_counter; // 待发送数据个数减1
if (++tx_rd_index == SIZE) tx_rd_index = 0; // 调整发送缓冲区队列指针
}
else SPI_free = 1; // 无待发送数据,置SPI空闲
if (++rx_wr_index == SIZE) rx_wr_index = 0; // 调整接收缓冲区队列指针
if (++rx_counter == SIZE)
{
rx_counter = 0;
rx_buffer_overflow = 1; // 接收数据溢出
}
}
unsigned char getSPIchar(void)
{
unsigned char data;
while (rx_counter == 0); //无接收数据,等待(死循环!)
data = SPI_rx_buff[rx_rd_index]; //从接收缓冲区取出一个SPI收到的数据
if (++rx_rd_index == SIZE) rx_rd_index = 0; //调整指针
#asm("cli")
--rx_counter;
#asm("sei")
return data;
}
void putSPIchar(unsigned char c)
{
while (tx_counter == SIZE); // 发送缓冲区满,等待
#asm("cli")
if (SPI_free)
{
SPDR = c; // SPI口空闲,直接放入SPDR由SPI口发送
SPI_free = 0; // 置SPI忙
}
else
{
SPI_tx_buffer[tx_wr_index] = c; // 将数据放入发送缓冲区排队
if (++tx_wr_index == SIZE) tx_wr_index = 0; //调整指针
++tx_counter;
}
#asm("sei")
}
void spi_init(void)
{
unsigned char temp;
DDRB |= 0xB0; // MISO为输入方式,MOSI,SCK和~SS为输出方式
PORTB |= 0x40; // MISO上拉电阻有效
SPCR = 0xD5; // SPI允许,主机模式,MSB方式,允许SPI中断,极性方式01,1/16系统时钟频率
SPSR = 0x00;
temp = SPSR;
temp = SPDR; // 清除SPI中断标志位,使SPI空闲
SPI_free = 1; // 置SPI空闲
}
void main(void)
{
unsigned char i;
#asm("cli") // 关中断
spi_init(); // 初始化SPI接口
#asm("sei") // 使能中断
while()
{
putSPIchar(i); // 通过SPI发送1字节
i++;
getSPIchar(); // 读取SPI接收的字节
………
}
}
在这个典型的SPI例程中,主程序中首先对SPI进行初始化,将PORTB的MOSI、SCLK和SS引脚作为输出,同时将MISO作为输入引脚,并打开上拉电阻。接着对SPI的寄存器进行初始化设置,并空读一次SPSR、SPDR寄存器(读SPSR后再对SPDR操作将自动清零SPI中断标志自动清零),使ISP空闲等待发送数据。
在SPI总线上,主机和从机构成一个相当于16位的循环移位寄存器构成(图15-2),当数据从主机方移出时,从机的数据同时也被移入,因此AVR的SPI数据发送和接收是在一个中断服务中完成。在SPI中断服务程序中,先从SPDR中读取一个由SPI串入接收到的字节,并存入接收数据缓冲队列中,再从发送数据缓冲队列中取出一个字节写入SPDR中,由ISP发送到从机。数据一旦写入SPDR,ISP硬件开始发送数据。等到ISP中断时,表示本次发送完成,并同时收到一个数据。类似第14章中的USART接口使用,程序中putSPIchar()和getSPIchar()为应用程序的中间层接口函数(SPI底层驱动程序为SPI的中断服务程序),分别使用了两组数据缓冲器构成循环队列。这种程序设计的思路,不但程序的结构性完整,同时也比较好的解决了高速MCU和低速串口之间的矛盾,便于实现程序中任务的并行执行,提高了MCU的运行效率。
本例程是一个通过ISP总线批量输出、输入数据的示例,用户可以使用一片ATmega16,将其MOSI和MISO两个引脚连接起来,构成一个ISP接口自发自收的系统,对程序进行演示验证。此时需要注意,本次中断中接收到的字节,实际为在上次中断中发出的数据。
在读懂及了解程序的基本处理思想和方法后,读者可以根据实际的需要对程序进行改动,以适合应用系统的使用。如在实际应用中,外接的从机是一片兼容SPI接口的温度芯片,控制时序为:主机先要连续发送3个字节的命令,然后从机才返回一个字节的数据。那么用户程序可以循环调用putSPIchar()函数4次,连续地将3个字节的命令和一个字节的空数据发送到从机,然后从接收数据缓冲器队列中连续读取4个字节,放弃掉前3个字节,第4个字节即为从机的返回数据了。
================================================================================
简单分析一下原因:
SPSR寄存器的最高位SPIF是SPI中断标志,它在SPI接口发送(接收)完成后才会被接口的硬件置为“1”。
仔细想过可以知道,SPIF为0和1都不能准确的判断出SPI口是否在发送数据,因为SPI不工作时候以及SPI正在发送数据,但没有发送完成,SPIF都是“0”。而SPIF为1时,也不说明SPI是否在工作(可能不工作了,也可能正在发送第2个数据),只是说明前一个字节发送完成了,同时也收到了一个数据,可以读了。
因此判断语句: if (tx_counter || ((SPSR & 0x80) == 0)) 后面对SPSR的条件判断显然是不正确的。 |
|