搜索
bottom↓
回复: 71

用AVR IO口 软件模拟I2C 软件包 + 说明

[复制链接]

出0入0汤圆

发表于 2007-9-6 10:19:50 | 显示全部楼层 |阅读模式
其实论坛中这样的驱动程序已经很多,如 EEPROM 驱动函数等,而且都做的很好,这里毕竟高手如云!!!但是我大概看了一下,似乎用AVR IO口做软件模拟I2C 的程序, 没有完全符合I2C规范的...憋了半天,觉得不吐不快,于是也动笔一叙,也管不得是否文采不好,术语不专业,鸡蛋满天飞了.

首先说明,对于I2C,AVR能用TWI就使用TWI,它相当于AVR内置硬件I2C接口,可靠性更高,节省软件运行开销.

I2C用IO模拟程序网上范例最多的就是51的程序了,这些范例的正确性无需怀疑.但是如果直接以它为蓝本将它"AVR化",一不留神,就会有点问题了.

这要从I2C的硬件规范和AVR及51单片机的IO口说起.I2C要求SCL,SDA二线都有 线与 功能,即I2C驱动口应该是 漏极开路 电路,其高电平的维持是靠上拉电阻来实现的, 而低电平则需要驱动口的 强下拉 能力.
51单片机IO口正好完全符合这个特性. 写起I2C驱动颇为得心应手.但是AVR的IO口强大了,它输出的高电平是实实在在的高电平,而不是靠什么上拉电阻来提供,只有10mA都不到的电流!于是如果直接使用 PORTB_Bit0 = 1这样的操作,就不能满足I2C的线与功能了,如果此时有别的设备要将SCL或者SDA拉低,那么结果就是二个IO口打架,谁赢谁输不得而知,时间长了,多半是两败俱伤,芯片发热吧.
当然AVR的IO口自然有办法满足I2C的电气特性要求,不就是不能输出1么,那么用它的高阻状态即可(DDRB_Bit0=0,PORTB_Bit0=0即可),要输出0么(DDRB_Bit0=1,PORTB_Bit0=0).

我网上看到的AVR软件模拟I2C的程序问题比较多的就是SCL的驱动,一般都直接用输出1和输出0来产生波形,这在一般应用中没问题,不过如果要用到主机仲裁,I2C芯片间肯定是要打架的!

I2C的上层建筑函数很丰富,我就给个最基本的I2C软件模拟函数,完全符合I2C规范.

补充以下,下面的程序用的是IAR.它控制单IO口比较方便.CVAVR也有这样的功能如 PORTB.0 = 1. 其它的编译器就只能用位操作了...

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

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

出0入0汤圆

 楼主| 发表于 2007-9-6 10:20:46 | 显示全部楼层
/* =================  I N C L U D E   F I L E  ============================
*
* File Name        : I2C_DRV.h
* Description      : 使用IO口模拟I2C总线. 完全符合I2C规范.
*                    需要延时函数 DELAY_US().
*
* Redactor / Data  : mousie / 2007-6-29
*                  - V1.0 模拟I2C总线,主机模式.支持AVR芯片.
* Revisor  / Data  :
*                  - V1.0 尚未测试.
*
* ===================================================================== */
#ifndef   __I2C_DRV_H
#define   __I2C_DRV_H

/* =================  I N C L U D E   F I L E  ========================= */
#include  "STATUS_SYSTEM\_SS_SERVE.h"

/* =================  M A C R O   D E F I N E  ========================= */
/**************************************************************************
Comment   : I2C引脚定义
**************************************************************************/
#define   IO_I2C_SCL               DDRD_Bit0
#define   PO_I2C_SCL               PORTD_Bit0
#define   IO_I2C_SDA               DDRD_Bit1
#define   PI_I2C_SDA               PIND_Bit1
#define   PO_I2C_SDA               PORTD_Bit1

/**************************************************************************
Comment   : I2C时钟频率,单位为Khz.
Option    : 1 - 250Khz
Warning   : 标准模式最高频率为100Khz, 常用5-10K的上拉电阻.
            快速模式最高频率为400Khz, 常用2-5K的上拉电阻.
            频率很低时可以直接使用AVR的内部上拉.
**************************************************************************/
#define   I2C_CLOCK_FREQUENCE      100

/**************************************************************************
Comment   : 是否使用AVR内部电阻作为I2C总线的上拉电阻.
Option    : 0 -- 不使用  1 -- 使用
**************************************************************************/
#define   PULL_UP_RESISTANCE       0

// 应答信号宏定义
#define   I2C_ACK                  0
#define   I2C_NO_ACK               1

/* =================  T Y P E     D E F I N E  ========================= */

/* =================  E X T E R N   G L O B A L E   V A R I A B L E  === */

/* =================  E X T E R N   F U N C T I O N  =================== */
extern void I2CInit( void );
extern void I2CStart( void );
extern void I2CStop( void );
extern U08  I2CTransmitByte( U08 data );
extern U08  I2CReceiveByte( U08 ack );


#endif
/* =================  T H E   E N D  =================================== */

出0入0汤圆

 楼主| 发表于 2007-9-6 10:22:01 | 显示全部楼层
/* =================  S O U R C E   F I L E  ==============================
*
* File Name        : I2C_DRV.c
* Description      : 使用IO口模拟I2C总线. 完全符合I2C规范.
*                    需要延时函数 DELAY_US().
*
* Redactor / Data  : mousie / 2007-6-29
*                  - V1.0 模拟I2C总线,主机模式.支持AVR芯片.
* Revisor  / Data  :
*                  - V1.0 测试完成,功能正确.
*
* ===================================================================== */

/* =================  I N C L U D E   F I L E  ========================= */
#include  "I2C_DRV.h"

/* =================  M A C R O   D E F I N E  ========================= */
#define   SCL_HIGH()               {IO_I2C_SCL = 0; PO_I2C_SCL = PULL_UP_RESISTANCE;}
#define   SCL_LOW()                {IO_I2C_SCL = 1; PO_I2C_SCL = 0;}
#define   SDA_HIGH()               {IO_I2C_SDA = 0; PO_I2C_SDA = PULL_UP_RESISTANCE;}
#define   SDA_LOW()                {IO_I2C_SDA = 1; PO_I2C_SDA = 0;}

#if  ((I2C_CLOCK_FREQUENCE > 400) || (I2C_CLOCK_FREQUENCE == 0))
  #error "I2C_CLOCK_FREQUENCE overflow!!!"
#elif (I2C_CLOCK_FREQUENCE > 250)
  #define I2C_DELAY_TIME           2
#else
  #define I2C_DELAY_TIME           (500 / I2C_CLOCK_FREQUENCE)
#endif

/* =================  G L O B A L E   V A R I A B L E  ================= */

/* =================  S T A T I C   G L O B A L E   V A R I A B L E  === */

/* =================  S T A T I C   F U N C T I O N  =================== */


/* ------------------------------------------------------------------------
* Function Name    : I2CInit()
* Description      : I2C IO口 初始化
*
* Input  Parameter : No Parameter
* Output Parameter : No Parameter
* Global Parameter : No Parameter
* --------------------------------------------------------------------- */
void I2CInit( void )
{
  SCL_HIGH();
  SDA_HIGH();
}

/* ------------------------------------------------------------------------
* Function Name    : I2CStart()
* Description      : I2C起始信号
*
* Input  Parameter : No Parameter
* Output Parameter : No Parameter
* Global Parameter : No Parameter
* --------------------------------------------------------------------- */
void I2CStart( void )
{
  SCL_HIGH();
  SDA_HIGH();
  DELAY_US( I2C_DELAY_TIME );
  SDA_LOW();
  DELAY_US( I2C_DELAY_TIME );
}

/* ------------------------------------------------------------------------
* Function Name    : I2CStop()
* Description      : I2C结束信号
*
* Input  Parameter : No Parameter
* Output Parameter : No Parameter
* Global Parameter : No Parameter
* --------------------------------------------------------------------- */
void I2CStop( void )
{
  SCL_LOW();
  DELAY_US( I2C_DELAY_TIME );
  SDA_LOW();
  DELAY_US( I2C_DELAY_TIME );
  SCL_HIGH();
  DELAY_US( I2C_DELAY_TIME );
  SDA_HIGH();
  DELAY_US( I2C_DELAY_TIME );
}

/* ------------------------------------------------------------------------
* Function Name    : I2CTransmitByte()
* Description      : 通过I2C发送一个字节的数据.
*
* Input  Parameter : data   -- 要发送的数据
* Output Parameter : 返回值 -- 返回应答信号
* Global Parameter : No Parameter
* --------------------------------------------------------------------- */
U08 I2CTransmitByte( U08 data )
{
  U08 i;

  for(i=0; i<8; i++)                                                       // 发送一个字节的数据
  {
    SCL_LOW();
    DELAY_US( I2C_DELAY_TIME );
    if (data & 0x80)
    {
      SDA_HIGH();
    }
    else
    {
      SDA_LOW();
    }
    DELAY_US( I2C_DELAY_TIME );
    SCL_HIGH();
    DELAY_US( I2C_DELAY_TIME );
    data <<= 1;
    DELAY_US( I2C_DELAY_TIME );
  }

  SCL_LOW();                                                               // 准备接收应答信号
  SDA_HIGH();                                                              // 使SDA为输入状态
  DELAY_US( I2C_DELAY_TIME );
  DELAY_US( I2C_DELAY_TIME );
  SCL_HIGH();
  DELAY_US( I2C_DELAY_TIME );
  DELAY_US( I2C_DELAY_TIME );
  if (PI_I2C_SDA == 0)                                                     // 从设备有应答
  {
    i = I2C_ACK;
  }
  else
  {
    i = I2C_NO_ACK;
  }

  return (i);
}

/* ------------------------------------------------------------------------
* Function Name    : I2CReceiveByte()
* Description      : 通过I2C接收一个字节的数据.
*
* Input  Parameter : ack    -- 要应答的信号
* Output Parameter : 返回值 -- 接收到的数据
* Global Parameter : No Parameter
* --------------------------------------------------------------------- */
U08 I2CReceiveByte( U08 ack )
{
  U08 i;
  U08 data = 0;

  SCL_LOW();
  SDA_HIGH();                                                              // 使SDA处于输入状态
  for (i=0; i < 8; i++)
  {
    SCL_LOW();
    DELAY_US( I2C_DELAY_TIME );
    DELAY_US( I2C_DELAY_TIME );
    SCL_HIGH();
    DELAY_US( I2C_DELAY_TIME );
    data <<= 1;
    if (PI_I2C_SDA)
    {
      ++data;
    }
    DELAY_US( I2C_DELAY_TIME );
  }

  SCL_LOW();                                                               // 主设备应答信号
  DELAY_US( I2C_DELAY_TIME );
  if (ack == I2C_ACK)
  {
    SDA_LOW();
  }
  else
  {
    SDA_HIGH();
  }
  DELAY_US( I2C_DELAY_TIME );
  SCL_HIGH();
  DELAY_US( I2C_DELAY_TIME );
  DELAY_US( I2C_DELAY_TIME );

  return ( data );
}


/* =================  T H E   E N D  =================================== */

出0入0汤圆

发表于 2007-9-6 12:03:14 | 显示全部楼层
楼主继续

出0入0汤圆

发表于 2007-9-6 12:17:09 | 显示全部楼层
楼主能不能做个模拟i2c slave的驱动,o(∩_∩)o...是不是要求太过了

出0入0汤圆

 楼主| 发表于 2007-9-7 09:35:52 | 显示全部楼层
enci,你可以自己做个练习啊,其实slave的CLK的IO口只要设为输入就可以了.比master简单.

shaoshunda要继续什么呢?要不我就把这帖子变成通讯驱动专帖,再申个精得了...可惜标题不能改了...

出0入0汤圆

 楼主| 发表于 2007-9-7 09:44:53 | 显示全部楼层
决定把我手上有的通过测试的通讯接口程序都传上来了.
阿莫可以改大标题的话帮忙改下吧.

下面是 UART 通讯接口,当然用了AVR的硬件UART,不过我也看到过有人用软件模拟UART的.....
此UART基本结构来自于马老师推荐的CVAVR UART程序.我略做修改,基本思路是一样的.
就是UART收发都有缓冲区.对UART的读写都是经过缓冲区的,其好处我就不多说了.可以找一找马老师对CVAVR自动生成之UART的评论.

不过下面的程序只提供了最基本的函数UartTransmitByte()和UartReceiveByte().读写一串和printf之类的函数有兴趣的就当作练习吧.

出0入0汤圆

 楼主| 发表于 2007-9-7 09:45:25 | 显示全部楼层
/* =================  I N C L U D E   F I L E  ============================
*
* File Name        : UART_DRV.h
* Description      : UART驱动程序.
*                    使用异步正常模式,中断缓冲方式.
*                    8 Data, 1 Stop, No Parity.
*
* Redactor / Data  : mousie / 2007-5-8
*                  - V1.0 提供了UART单字符输入输出函数.
* Revisor  / Data  : mousie / 2007-5-10
*                  - V1.0 所有功能测试完成,可正常工作.稳定性未测试.
*                  : mousie / 2007-7-2
*                  - V1.1 提供缓冲器的初始化函数.
*
* ===================================================================== */
#ifndef   __UART_DRV_H
#define   __UART_DRV_H

/* =================  I N C L U D E   F I L E  ========================= */
#include  "STATUS_SYSTEM\_SS_SERVE.h"

/* =================  M A C R O   D E F I N E  ========================= */
/**************************************************************************
Comment   : 设置芯片拥有的 UART 数量.
Option    : 1  2  3
**************************************************************************/
#define   __UART_NUM               1

/**************************************************************************
Comment   : 定义 UART 的波特率.
Option    : 1MHz  系统时钟常用波特率为 2400、4800 .
            2MHz  系统时钟常用波特率为 2400、4800、9600 .
            4MHz  系统时钟常用波特率为 2400、4800、9600、19200 .
            8MHz  系统时钟常用波特率为 2400、4800、9600、19200 .
            16MHz 系统时钟常用波特率为 2400、4800、9600、19200 .
**************************************************************************/
#define   UART_BAUD                9600UL

/**************************************************************************
Comment   : 定义接受和发送缓冲区的大小.
Option    : 有效值为 1 -- 65535
**************************************************************************/
#define   RX_BUFFER_SIZE           8
#define   TX_BUFFER_SIZE           8

// UartReceiveByte 的返回值
#define   UART_INPUT_ERROR         0x00
#define   UATT_BUFFER_VALID        0x55
#define   UART_BUFFER_OVERFLOW     0x99
#define   UART_BUFFER_EMPTY        0xFF

/* =================  T Y P E     D E F I N E  ========================= */

/* =================  E X T E R N   G L O B A L E   V A R I A B L E  === */

/* =================  E X T E R N   F U N C T I O N  =================== */
extern void UartInit( void );
extern void UartTxBufferInit( void );
extern void UartRxBufferInit( void );
extern void UartTransmitByte( U08 data );

/* ------------------------------------------------------------------------
Comment   : UART 接受函数.
Range     : 该函数的返回值如下.
            UART_BUFFER_EMPTY    -- 接受缓冲区空,没有接受到UART数据.
            UART_BUFFER_OVERFLOW -- 接受缓冲区溢出,调用函数后会清除溢出标志.
            UART_BUFFER_VALID    -- 接受缓冲区数据有效.
Warning   : 接受到的UART数据不是由返回值返回,而是放在 *ptrDst 中!
------------------------------------------------------------------------ */
extern U08  UartReceiveByte( U08 *ptrDst );


#endif
/* =================  T H E   E N D  =================================== */

出0入0汤圆

 楼主| 发表于 2007-9-7 09:46:04 | 显示全部楼层
/* =================  S O U R C E   F I L E  ==============================
*
* File Name        : UART_DRV.c
* Description      : UART驱动程序.
*                    使用异步正常模式,中断缓冲方式.
*                    8 Data, 1 Stop, No Parity.
*
* Redactor / Data  : mousie / 2007-5-8
*                  - V1.0 提供了UART单字符输入输出函数.
* Revisor  / Data  : mousie / 2007-5-10
*                  - V1.0 所有功能测试完成,可正常工作.稳定性未测试.
*                  : mousie / 2007-7-2
*                  - V1.1 提供缓冲器的初始化函数.
*
* ===================================================================== */

/* =================  I N C L U D E   F I L E  ========================= */
#include  "UART_DRV.h"

/* =================  M A C R O   D E F I N E  ========================= */
// 波特率寄存器值计算
#define   UART_BAUD_REGISTER       (SYSTEM_CLOCK / (UART_BAUD * 16) - 1)

// 寄存器及标志位设置.
#if (__UART_NUM == 1)
  #define UART_STATUS              UCSRA                                   // UART状态寄存器
  #define UART_DATA                UDR                                     // UART收发数据寄存器

  #define FRAMING_ERROR            BIT( FE   )                             // 设置标记位
  #define PARITY_ERROR             BIT( PE   )
  #define DATA_OVERRUN             BIT( DOR  )
  #define DATA_REGISTER_EMPTY      BIT( UDRE )
  #define RX_COMPLETE              BIT( RXC  )
#else
  #define UART_STATUS              UCSR0A                                  // UART状态寄存器
  #define UART_DATA                UDR0                                    // UART收发数据寄存器

  #define FRAMING_ERROR            BIT( FE0   )                            // 设置标记位
  #define PARITY_ERROR             BIT( PE0   )
  #define DATA_OVERRUN             BIT( DOR0  )
  #define DATA_REGISTER_EMPTY      BIT( UDRE0 )
  #define RX_COMPLETE              BIT( RXC0  )
#endif

/* =================  G L O B A L E   V A R I A B L E  ================= */

/* =================  S T A T I C   G L O B A L E   V A R I A B L E  === */
  volatile static U08 rxBuffer[ RX_BUFFER_SIZE ];                          // 接受缓冲区相关定义
#if (RX_BUFFER_SIZE < 256)
  volatile static U08 rxWriteIndex;
  volatile static U08 rxReadIndex;
  volatile static U08 rxCounter;
#else
  volatile static U16 rxWriteIndex;
  volatile static U16 rxReadIndex;
  volatile static U16 rxCounter;
#endif
  volatile static BOOL rxBufferOverflow;                                   // 接受缓冲区溢出标志

  volatile static U08 txBuffer[ TX_BUFFER_SIZE ];                          // 发送缓冲区相关定义
#if (TX_BUFFER_SIZE < 256)
  volatile static U08 txWriteIndex;
  volatile static U08 txReadIndex;
  volatile static U08 txCounter;
#else
  volatile static U16 txWriteIndex;
  volatile static U16 txReadIndex;
  volatile static U16 txCounter;
#endif

/* =================  S T A T I C   F U N C T I O N  =================== */


/* ------------------------------------------------------------------------
* Function Name    : UartRxIsr()
* Description      : UART接受数据中断.
*
* Input  Parameter : No Parameter
* Output Parameter : No Parameter
* Global Parameter : Uart Driver的全局变量
* --------------------------------------------------------------------- */
#if (__UART_NUM == 1)
  #pragma vector = USART_RXC_vect
#else
  #pragma vector = USART0_RXC_vect
#endif
  __interrupt void UartRxIsr( void )
{
  U08 status;
  U08 data;

  status = UART_STATUS;
  data   = UART_DATA;
  if ( (status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN)) == 0 )
  {
    rxBuffer[ rxWriteIndex ] = data;
    if (++rxWriteIndex >= RX_BUFFER_SIZE)
    {
      rxWriteIndex = 0;
    }
    if (++rxCounter >= RX_BUFFER_SIZE)
    {
      rxCounter        = 0;
      rxBufferOverflow = 1;
    }
  }
}

/* ------------------------------------------------------------------------
* Function Name    : UartTxIsr()
* Description      : UART发送数据中断.
*
* Input  Parameter : No Parameter
* Output Parameter : No Parameter
* Global Parameter : Uart Driver的全局变量
* --------------------------------------------------------------------- */
#if (__UART_NUM == 1)
  #pragma vector = USART_TXC_vect
#else
  #pragma vector = USART0_TXC_vect
#endif
  __interrupt void UartTxIsr( void )
{
  if ( txCounter )
  {
    --txCounter;
    UART_DATA = txBuffer[ txReadIndex ];
    if (++txReadIndex >= TX_BUFFER_SIZE)
    {
      txReadIndex = 0;
    }
  }
}

/* ------------------------------------------------------------------------
* Function Name    : UartInit()
* Description      : Uart初始化函数.
*
* Input  Parameter : No Parameter
* Output Parameter : No Parameter
* Global Parameter : Uart Driver的全局变量
* --------------------------------------------------------------------- */
void UartInit( void )
{
  rxWriteIndex     = 0;
  rxReadIndex      = 0;
  rxCounter        = 0;
  rxBufferOverflow = 0;
  txWriteIndex     = 0;
  txReadIndex      = 0;
  txCounter        = 0;

  UART_STATUS = 0x00;
#if (__UART_NUM == 1)
  UCSRB  = BIT( RXCIE ) | BIT( TXCIE ) | BIT( RXEN ) | BIT( TXEN );
  UCSRC  = BIT( UCSZ1 ) | BIT( UCSZ0 );
  UBRRH  = ( UART_BAUD_REGISTER >> 8   );
  UBRRL  = ( UART_BAUD_REGISTER & 0xFF );
#else
  UCSR0B = BIT( RXCIE0 ) | BIT( TXCIE0 ) | BIT( RXEN0 ) | BIT( TXEN0 );
  UCSR0C = BIT( UCSZ01 ) | BIT( UCSZ00 );
  UBRR0H = ( UART_BAUD_REGISTER >> 8   );
  UBRR0L = ( UART_BAUD_REGISTER & 0xFF );
#endif
}

/* ------------------------------------------------------------------------
* Function Name    : UartTxBufferInit()
* Description      : Uart发送缓冲器初始化函数.
*
* Input  Parameter : No Parameter
* Output Parameter : No Parameter
* Global Parameter : Uart Driver的全局变量
* --------------------------------------------------------------------- */
void UartTxBufferInit( void )
{
  txWriteIndex = 0;
  txReadIndex  = 0;
  txCounter    = 0;
}

/* ------------------------------------------------------------------------
* Function Name    : UartRxBufferInit()
* Description      : Uart接收缓冲器初始化函数.
*
* Input  Parameter : No Parameter
* Output Parameter : No Parameter
* Global Parameter : Uart Driver的全局变量
* --------------------------------------------------------------------- */
void UartRxBufferInit( void )
{
  rxWriteIndex     = 0;
  rxReadIndex      = 0;
  rxCounter        = 0;
  rxBufferOverflow = 0;
}

/* ------------------------------------------------------------------------
* Function Name    : UartReceiveByte()
* Description      : 接受一个UART传来的数据.
*
* Input  Parameter : ptrDst -- 接受数据的存放地址
* Output Parameter : return -- 返回接受缓冲区的情况
* Global Parameter : Uart Driver的全局变量
* --------------------------------------------------------------------- */
U08 UartReceiveByte( U08 *ptrDst )
{
#if (__PARAMETER_CHECK_EN > 0) || (__DEBUG_MODE_EN > 0)
  if (ptrDst == NULL)
  {
    ASSERT ( UART_DRV_INPUT_PTR_NULL);
    return ( UART_INPUT_ERROR );
  }
#endif

  if (rxBufferOverflow > 0)
  {
    rxBufferOverflow = 0;
    return ( UART_BUFFER_OVERFLOW );
  }

  CLI();
  if (rxCounter == 0)
  {
    SEI();
    return ( UART_BUFFER_EMPTY );
  }
  SEI();

  *ptrDst = rxBuffer[ rxReadIndex ];
  if (++rxReadIndex >= RX_BUFFER_SIZE)
  {
    rxReadIndex = 0;
  }
  CLI();
  --rxCounter;
  SEI();

  return ( UATT_BUFFER_VALID );
}

/* ------------------------------------------------------------------------
* Function Name    : UartTransmitByte()
* Description      : 通过UART发送一个数据.
*
* Input  Parameter : data -- 要发送的数据
* Output Parameter : No Parameter
* Global Parameter : Uart Driver的全局变量
* --------------------------------------------------------------------- */
void UartTransmitByte( U08 data )
{
#if (TX_BUFFER_SIZE < 256)
  U08 txCounterTemp;
#else
  U16 txCounterTemp;
#endif
  do
  {
    CLI();
    txCounterTemp = txCounter;
    SEI();
  }
  while (txCounterTemp >= TX_BUFFER_SIZE);

  if ( txCounterTemp || ((UART_STATUS & DATA_REGISTER_EMPTY) == 0) )
  {
    txBuffer[ txWriteIndex ] = data;
    if (++txWriteIndex == TX_BUFFER_SIZE)
    {
      txWriteIndex = 0;
    }
    CLI();
    ++txCounter;
    SEI();
  }
  else                                                                     // 缓冲区和发送寄存器都为空
  {
    UART_DATA = data;                                                      // 直接放至UART数据寄存器
  }
}


/* =================  T H E   E N D  =================================== */

出0入0汤圆

 楼主| 发表于 2007-9-7 09:54:43 | 显示全部楼层
下面的是IO口软件模拟SPI的程序.目前写的比较规范化的通讯程序就这几个.各位达人好好心,也把珍藏多年的通讯底层驱动贴上来看看吧.我替大家谢过了:)

/* =================  I N C L U D E   F I L E  ============================
*
* File Name        : SPI_DRV.h
* Description      : SPI传输协议驱动函数.
*                    需要延时函数 DELAY_US().
*
* Redactor / Data  : mousie / 2007-5-8
*                  - V1.0 主机模式,一个字节的发送和接受,高电平数据有效.
* Revisor  / Data  :
*                  - V1.0 尚未测试.
*
* ===================================================================== */
#ifndef   __SPI_DRV_H
#define   __SPI_DRV_H

/* =================  I N C L U D E   F I L E  ========================= */
#include  "STATUS_SYSTEM\_SS_SERVE.h"

/* =================  M A C R O   D E F I N E  ========================= */
/**************************************************************************
Comment   : SPI引脚定义
**************************************************************************/
#define   IO_SPI_SS                DDRD_Bit0
#define   PO_SPI_SS                PORTD_Bit0
#define   IO_SPI_SCLK              DDRD_Bit1
#define   PO_SPI_SCLK              PORTD_Bit1
#define   IO_SPI_MOSI              DDRD_Bit2
#define   PO_SPI_MOSI              PORTD_Bit2
#define   IO_SPI_MISO              DDRD_Bit3
#define   PI_SPI_MISO              PIND_Bit3

/**************************************************************************
Comment   : 发送模式定义
Option    : 1 表示MSB模式发送,0 表示LSB模式发送
**************************************************************************/
#define   __SPI_MSB_EN             1

/**************************************************************************
Comment   : SPI时钟频率设置,单位为Khz.
Option    : 1 - 500 Khz.
**************************************************************************/
#define   SPI_CLOCK_FREQUENCY      200

/* =================  T Y P E     D E F I N E  ========================= */

/* =================  E X T E R N   G L O B A L E   V A R I A B L E  === */

/* =================  E X T E R N   F U N C T I O N  =================== */
#define   SpiBegin()               { PO_SPI_SS = 0; }                      // SPI启动
#define   SpiEnd()                 { PO_SPI_SS = 1; }                      // SPI停止

void SpiInit( void );
U08  SpiData( U08 send );                                                  // SPI收发数据


#endif
/* =================  T H E   E N D  =================================== */

出0入0汤圆

 楼主| 发表于 2007-9-7 09:55:24 | 显示全部楼层
/* =================  S O U R C E   F I L E  ==============================
*
* File Name        : SPI_DRV.c
* Description      : SPI传输协议驱动函数.
*                    需要延时函数 DELAY_US().
*
* Redactor / Data  : mousie / 2007-5-8
*                  - V1.0 主机模式,一个字节的发送和接受,高电平数据有效.
* Revisor  / Data  :
*                  - V1.0 尚未测试.
*
* ===================================================================== */

/* =================  I N C L U D E   F I L E  ========================= */
#include  "SPI_DRV.h"

/* =================  M A C R O   D E F I N E  ========================= */
#if  ((SPI_CLOCK_FREQUENCY > 500) || (SPI_CLOCK_FREQUENCY == 0))
  #error "SPI_CLOCK_FREQUENCY overflow!!!"
#else
  #define SPI_CLOCK_TIME           (500 / I2C_CLOCK_FREQUENCE)
#endif

/* =================  G L O B A L E   V A R I A B L E  ================= */

/* =================  S T A T I C   G L O B A L E   V A R I A B L E  === */

/* =================  S T A T I C   F U N C T I O N  =================== */


/* ------------------------------------------------------------------------
* Function Name    : SpiInit()
* Description      : SPI 初始化函数.
*
* Input  Parameter : No Parameter
* Output Parameter : No Parameter
* Global Parameter : No Parameter
* --------------------------------------------------------------------- */
void SpiInit( void )
{
  PO_SPI_SS   = 1;
  IO_SPI_SS   = 1;

  PO_SPI_SCLK = 1;
  IO_SPI_SCLK = 1;

  PO_SPI_MOSI = 1;
  IO_SPI_MOSI = 1;

  PI_SPI_MISO = 0;
  IO_SPI_MISO = 0;
}

/* ------------------------------------------------------------------------
* Function Name    : SpiData()
* Description      : SPI读写函数,使用IO口模拟SPI时序.
*
* Input  Parameter : send    -- 要发送的字节数据
* Output Parameter : receive -- 接受到的字节数据
* Global Parameter : No Parameter
* --------------------------------------------------------------------- */
U08 SpiData( U08 send )
{
  U08 i;
  U08 receive = 0;

  PO_SPI_MOSI = 1;
  for (i = 0; i < 8; i++)
  {
    PO_SPI_SCLK = 0;
#if __SPI_MSB_EN > 0
    PO_SPI_MOSI = send & 0x80;
    send <<= 1;
#else
    PO_SPI_MOSI = send & 0x01;
    send >>= 1;
#endif
    Delay_us( SPI_CLOCK_TIME );

    PO_SPI_SCLK = 1;
#if __SPI_MSB_EN > 0
    receive <<= 1;
#else
    receive >>= 1;
#endif
    if (PI_SPI_MISO)
    {
#if __SPI_MSB_EN > 0
      receive |= 0x01;
#else
      receive |= 0x80;
#endif
    }
    Delay_us( SPI_CLOCK_TIME );
  }
  return receive;
}


/* =================  T H E   E N D  =================================== */

出0入0汤圆

 楼主| 发表于 2007-9-7 09:57:20 | 显示全部楼层
说明一下,所有的参数在头文件中修改就可以了.
以下种方式注明的说明是可以修改的参数.
/**************************************************************************
Comment   : 发送模式定义
Option    : 1 表示MSB模式发送,0 表示LSB模式发送
**************************************************************************/

程序已经写的很规范了,应该很容易看明白的.

出0入0汤圆

发表于 2007-9-7 12:54:11 | 显示全部楼层
非常感谢

出0入10汤圆

发表于 2007-9-7 23:55:00 | 显示全部楼层
I2C用IO模拟程序经常用,就没考虑这么仔细,谢谢楼主。

出0入0汤圆

发表于 2007-9-8 22:19:30 | 显示全部楼层
有时候感觉用IO口模拟I2C比AVR本身带的硬件方便,呵呵.

平时用都没注意楼主说的问题,谢谢楼主了

出0入0汤圆

发表于 2007-9-8 23:21:23 | 显示全部楼层
slave IIC程序好做吗?应该不好作吧。
在通讯里面,master是最好作的,因为master是时钟的控制者和发出者。
slave的话就麻烦,用中断去做?

出0入0汤圆

 楼主| 发表于 2007-9-11 19:34:48 | 显示全部楼层
恩,slave只能用中断做。(查询就免了。。)。不过一般好像用的不多

出0入0汤圆

发表于 2007-9-21 23:53:49 | 显示全部楼层
楼主,你是我的偶像,,我想拜你为师,你的一句话,让我收益非浅啊,
我是作卫星通讯的,现在我们就用mega128,我也是刚刚接触 AVR 的芯片。
我的邮箱: totel@163.com,希望能交你这个朋友!!

出0入0汤圆

发表于 2007-10-10 21:16:35 | 显示全部楼层
楼主 draapho:能不能讲将I2C里面线与功能是什么意思;
               数电里面的线与功能是2条线同时为高,则输出为高,否则为低,这与
I2C总线SCL,SDA有什么关系?
               期求解答!

出0入0汤圆

发表于 2007-10-10 22:25:05 | 显示全部楼层
在读24C02时,主机是不需要在读完一个字节后加ACK,我看楼主的程序里加了。

出0入0汤圆

发表于 2008-4-5 16:03:27 | 显示全部楼层
记号。

出0入46汤圆

发表于 2008-4-6 09:46:20 | 显示全部楼层
不错

出0入0汤圆

发表于 2008-5-4 12:26:25 | 显示全部楼层
呵呵,谢了

出0入0汤圆

发表于 2008-8-16 16:47:48 | 显示全部楼层
记号

出0入0汤圆

发表于 2008-8-16 21:24:08 | 显示全部楼层
mark

出0入0汤圆

发表于 2008-9-7 19:07:59 | 显示全部楼层
不错

出0入0汤圆

发表于 2008-9-7 20:30:08 | 显示全部楼层
不错

出0入0汤圆

发表于 2008-9-8 09:28:42 | 显示全部楼层
路过,有意思,记号

出0入0汤圆

发表于 2009-4-25 19:13:16 | 显示全部楼层
多路I2C主接口有可能做吗?

出0入0汤圆

发表于 2009-11-24 19:40:21 | 显示全部楼层
收益了!谢谢LZ

出0入0汤圆

发表于 2009-12-7 11:45:56 | 显示全部楼层
LZ请教一个问题,你的头文件中提到的#include  "STATUS_SYSTEM\_SS_SERVE.h"
这个文件在哪里?

出0入0汤圆

发表于 2009-12-28 10:20:03 | 显示全部楼层
刚好合用,谢谢2年前发帖的楼主。

出0入0汤圆

发表于 2009-12-28 15:28:25 | 显示全部楼层
挑个小错:
U08 I2CTransmitByte( U08 data ) 函数中,发送完8个数据之后,紧接着下一个周期要等到slave的ACK信号,这个过程master需要做的是:

step1:SLC拉低,SDA输入;
step2:再把SLC拉高;
step3:最后把SLC拉低,

经过3步之后,才可以检测到ACK信号而楼主的程序只做了前面两步。如果不进行step3的话,与某些严谨的I2C器件通信会出错。

出0入0汤圆

发表于 2009-12-28 16:13:24 | 显示全部楼层
总是感觉AVR硬件TWI的设置太麻烦

出0入0汤圆

发表于 2010-3-12 19:18:42 | 显示全部楼层
不错的,分享就有收获,你的BUG也找出来了

出0入0汤圆

发表于 2010-3-13 10:49:47 | 显示全部楼层
用TWI,还不如模拟好用

出0入0汤圆

发表于 2010-3-25 12:35:56 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-4-24 21:58:16 | 显示全部楼层
记号

出0入0汤圆

发表于 2010-4-28 21:42:09 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-6-10 00:09:48 | 显示全部楼层

出0入0汤圆

发表于 2010-6-11 00:48:58 | 显示全部楼层
记号
头像被屏蔽

出0入0汤圆

发表于 2010-6-12 22:50:57 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽

出0入0汤圆

发表于 2010-6-18 00:49:01 | 显示全部楼层

出0入0汤圆

发表于 2010-6-18 09:03:47 | 显示全部楼层
jihao

出0入0汤圆

发表于 2010-7-16 15:33:38 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-7-24 17:03:18 | 显示全部楼层
xuexi

出0入0汤圆

发表于 2010-9-17 17:50:19 | 显示全部楼层
mark!!!!!!!!!!!!!!

出0入0汤圆

发表于 2010-9-17 22:14:31 | 显示全部楼层
MARK

出0入0汤圆

发表于 2010-9-17 23:38:34 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-9-18 12:56:28 | 显示全部楼层
收益了!谢谢LZ

出0入0汤圆

发表于 2010-11-3 12:59:56 | 显示全部楼层
谢谢

出0入0汤圆

发表于 2010-12-24 15:42:57 | 显示全部楼层
记号。

出0入0汤圆

发表于 2010-12-24 16:39:38 | 显示全部楼层
MARK

出0入0汤圆

发表于 2011-8-11 14:09:34 | 显示全部楼层
先记下

出0入0汤圆

发表于 2011-8-14 23:12:51 | 显示全部楼层
正是需要!

出0入0汤圆

发表于 2011-9-21 15:16:10 | 显示全部楼层
正是看到这里,心里疑惑,于是度娘,发现此贴,楼主一解我心中疑惑,感谢万分

出0入0汤圆

发表于 2011-9-25 10:07:10 | 显示全部楼层
正好使用

出0入0汤圆

发表于 2011-9-28 14:47:02 | 显示全部楼层
正好使用

出0入0汤圆

发表于 2011-9-28 17:14:51 | 显示全部楼层
MARK

出0入0汤圆

发表于 2011-10-6 15:05:36 | 显示全部楼层
mark

出50入10汤圆

发表于 2011-12-6 11:04:30 | 显示全部楼层
MARK

出0入0汤圆

 楼主| 发表于 2011-12-6 14:18:07 | 显示全部楼层
回复【33楼】loopzhong 哥布林工程师
-----------------------------------------------------------------------

谢谢提醒。今年用STM32的时候吃了亏了。就因为这个问题,查了一周。
最后查明就是因为没有把CLK拉低,把CLK控制权释放给从设备。还是没吃透I2C规范啊。。。

出0入0汤圆

发表于 2011-12-6 23:24:02 | 显示全部楼层
mark

出0入0汤圆

发表于 2012-2-2 17:22:13 | 显示全部楼层
AVR I2C SPI MARK

出0入0汤圆

发表于 2013-6-3 16:58:35 | 显示全部楼层
准备使用模拟i2c,学习了,谢谢lz。。。

出0入0汤圆

发表于 2013-8-2 10:22:23 | 显示全部楼层
论坛里的高手就是多,感谢楼主分享,也丰常感谢挑错的大神,小弟受益匪浅

出0入0汤圆

发表于 2013-8-14 11:18:22 | 显示全部楼层
作个记号,不顶不行。

出0入0汤圆

发表于 2013-8-14 15:28:28 | 显示全部楼层
我现在用IO模拟的I2C读取EEPROM,平时没有问题,但是大量读取数据的时候就会出错,不知道什么原因

出0入0汤圆

发表于 2014-6-24 07:35:28 | 显示全部楼层
好贴,一直用的硬件I2C,下次试试模拟

出0入4汤圆

发表于 2020-1-10 15:36:35 | 显示全部楼层
IIC SPI 软件模拟。多谢分享!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2024-4-27 08:26

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

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