搜索
bottom↓
回复: 7

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

[复制链接]

出0入0汤圆

发表于 2009-4-30 22:26:32 | 显示全部楼层 |阅读模式
今天我用proteus仿真测试《AVR单片机嵌入式系统原理与应用实践》中P443-P444页SPI的程序,但在仿真的时候SPI串口一直没有发出数据,我测试的完整程序如下
#include <mega16.h>

#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 = 0;
interrupt [SPI_STC] void spi_isr(void)
{           
    SPI_rx_buff[rx_wr_index] = SPDR;
    if(tx_counter)
    {
        SPDR = SPI_tx_buff[tx_rd_index];
        --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[rx_rd_index];
    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[tx_wr_index] = 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[4];
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[tx_wr_index] = 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仿真的时候就没问题了,只是在连续发送多个字节或发送单个字节时,第一个数据重复发送了一次。但这个程序还不够完善希望大家能提出更好的解决办法。

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入0汤圆

发表于 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[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接收的字节  
        ………  
      }  
    }

出0入8汤圆

发表于 2009-5-1 14:27:44 | 显示全部楼层
记号,谢谢马老师!!

出0入0汤圆

 楼主| 发表于 2009-5-2 17:36:04 | 显示全部楼层
增加了一个空闲标志,这样好多啦,我要认真琢磨琢磨。
谢谢马老师!!!

出0入0汤圆

发表于 2009-12-12 20:47:18 | 显示全部楼层
一头雾水

出0入0汤圆

发表于 2009-12-26 13:54:09 | 显示全部楼层
mark,我看了下,我买的书也是第一版第一次印刷的。

出0入0汤圆

发表于 2011-8-19 21:01:39 | 显示全部楼层
nsigned 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;   
    }   

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

出0入0汤圆

发表于 2011-8-20 01:58:58 | 显示全部楼层
to 6楼:

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

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

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

在许多的细节问题上,我的教程中都考虑的比较周全。这本冠以“国家级规划教材”的教科书,我可以毫不客气的说,决不是虚有其名。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-25 20:31

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表