zhoujiewen 发表于 2009-4-30 22:26:32

《AVR单片机嵌入式系统原理与应用实践》P444页 SPI程序有问题

今天我用proteus仿真测试《AVR单片机嵌入式系统原理与应用实践》中P443-P444页SPI的程序,但在仿真的时候SPI串口一直没有发出数据,我测试的完整程序如下
#include <mega16.h>

#define SIZE 100
unsigned char SPI_rx_buff;
unsigned char SPI_tx_buff;
unsigned char rx_wr_index,rx_rd_index,rx_counter,rx_buffer_overflow;
unsigned char tx_wr_index,tx_rd_index,tx_counter = 0;
interrupt void spi_isr(void)
{         
    SPI_rx_buff = SPDR;
    if(tx_counter)
    {
      SPDR = SPI_tx_buff;
      --tx_counter;
      if(++ tx_rd_index == SIZE) tx_rd_index = 0;
    }   
    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;
    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(tx_counter || (SPSR & 0x80) == 0){
      SPI_tx_buff = c;         
      if(++ tx_wr_index == SIZE) tx_wr_index = 0;
      ++ tx_counter;
    }
    else
            SPDR = c;
    #asm("sei")
}

void spi_init(void)
{
   char temp;
   DDRB |= 0xb0;
   PORTB |= 0x40;
   SPCR = 0xf5;
   SPSR = 0x00;
   temp = SPSR;
   temp = SPDR;//SPIF清零((SPSR & 0x80) == 0为真)
}
unsigned char buffer;
void main(void)
{
    unsigned char i = 0;
    #asm("cli")
    spi_init();
    #asm("sei")
   
    for(i = 1;i < 8;i ++){
      putSPIchar(i);
    }
    while(1);   
   
}
后来我仔细琢磨了一下程序发现putSPIchar有问题,问题就在if(tx_counter || (SPSR & 0x80) == 0),在初始化spi_init()中, 已经把SPSR中中断标志SPIF清零了(即(SPSR & 0x80) == 0为真)。当使用putSPIchar函数发送数据时,if语句的条件始终成立,此时数据只会存入缓冲区,程序执行不到else语句,数据始终没有放入SPDR中,所以数据一直都不能发送出去。我把程序稍微改动了一下:
void putSPIchar(unsigned char c)
{
    while(tx_counter == SIZE);
    #asm("cli")
    if(tx_counter || (SPSR & 0x80) == 0){
      SPI_tx_buff = c;
      if(++ tx_wr_index == SIZE) tx_wr_index = 0;
      ++ tx_counter;
      if(tx_counter == 1)   // 存入一个数据后立即跳到else,将数据C送入SPDR中启动SPI发数据。
              goto flag;
      
    }
   else{
         flag:
               SPDR = c;
}
程序修改后程序进行Protues仿真的时候就没问题了,只是在连续发送多个字节或发送单个字节时,第一个数据重复发送了一次。但这个程序还不够完善希望大家能提出更好的解决办法。

machao 发表于 2009-5-1 12:04:00

在第1次印刷出版的书中,此段代码是有问题的。
在第2次印刷出版的书中已经做了修改。

感谢LZ购买本书,你手中可能是第1次印刷版的,在本栏的置顶帖中,有对第1次印刷版的修改表,共有151处各种错误。这些错误在第2次印刷出版的书中全部做了修改。

下面是这段代码的修改版,请LZ测试评估一下。

5。***P443-P444,原代码里面有几处笔误,以及一个BUG,请改成如下:

    #define SIZE 100
    unsigned char SPI_rx_buff;
    unsigned char SPI_tx_buff;
    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 void spi_isr(void)          // SPI 完成中断服务
    {
      SPI_rx_buff = SPDR;            // 从SPI口读出收到的字节放入接收缓冲区
      if (tx_counter)                                        // 如果发送缓冲区中有待发的数据
      {
      SPDR = SPI_tx_buff;            // 发送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;                  // 从接收缓冲区取出一个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 = 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接收的字节
      ………
      }
    }

fangmcu 发表于 2009-5-1 14:27:44

记号,谢谢马老师!!

zhoujiewen 发表于 2009-5-2 17:36:04

增加了一个空闲标志,这样好多啦,我要认真琢磨琢磨。
谢谢马老师!!!

Ryan 发表于 2009-12-12 20:47:18

一头雾水

sunnyAVR 发表于 2009-12-26 13:54:09

mark,我看了下,我买的书也是第一版第一次印刷的。

zhouaheng 发表于 2011-8-19 21:01:39

nsigned char getSPIchar(void)   
    {   
      unsigned char data;   
      while (rx_counter == 0);                           // 无接收数据,等待(死循环!)   
      data = SPI_rx_buff;                  // 从接收缓冲区取出一个SPI收到的数据   
      if (++rx_rd_index == SIZE) rx_rd_index = 0;   // 调整指针   
      #asm("cli")   
      --rx_counter;   
      #asm("sei")   
      return data;   
    }   

#asm("cli")    各位看看 我认为将这句放在while 之后是否好一些期待讨论

machao 发表于 2011-8-20 01:58:58

to 6楼:

这个要看SPI的中断是如何写的。

用#asm("cli")关中断,是防止中断中也修改敏感变量造成的错误。
而变量rx_rd_index在SPI中断中并没有涉及(在其它中断中通常也不会涉及到的),只有rx_counter在SPI中断中涉及到,所以关闭中断放在调整rx_counter之前。

放在WHILE后也是可以的,但没有我书上的例子好。因为提前关闭全局中断,可能会影响其它中断的及时响应。在一个实际的系统中,可能不只是使用SPI中断,也会使用其它的中断,比如定时器中断,而且需要及时的响应(有更高的优先级)。如果过早的关闭全局中断,会影响其它中断的及时处理的。

在许多的细节问题上,我的教程中都考虑的比较周全。这本冠以“国家级规划教材”的教科书,我可以毫不客气的说,决不是虚有其名。
页: [1]
查看完整版本: 《AVR单片机嵌入式系统原理与应用实践》P444页 SPI程序有问题