从PIC18s上的I2C开始


8

对于一个项目,我希望三个PIC(两个从PIC18F4620,一个主PIC18F46K22)通过I2C总线进行通信。稍后,可以添加更多从设备(如EEP​​ROM,SRAM等)。我正在使用C18编译器在C中为这些PIC编写代码。我在Internet上浏览了很多东西,但是找不到用于处理(M)SSP外设的库。我已经在I2C模式下阅读了(M)SSP外设上的这两个PIC的数据表,但找不到如何与总线接口。

所以我需要主库从库。

您有什么推荐的吗?你在某处有这样的图书馆吗?它是内置在编译器中的吗?如果是,在哪里?网络上有什么好的教程吗?


2
几个月前,我遇到了类似的问题。您可以在这里阅读有关它们的信息。这是可与I ^ 2C一起使用的C18库,但缺少一件事:您需要通过写入适当的寄存器来手动设置总线速度,该库的文档中未提及。
AndrejaKo 2013年

谢谢,这很有帮助!但是,它仅充当主机部分,而不是从机部分。

是的,那时我不需要使用奴隶,所以没有奴隶的例子。抱歉。
AndrejaKo 2013年

2
不,那很好,对于母版这很有用!:-)

请同时在端口ANSELC = 0上禁用模拟;

Answers:


22

Microchip为此编写了应用笔记:

  • 实施I2C从机的AN734
  • 实施I2C主器件的AN735
  • 在设置网络协议进行环境监控方面,还有更理论的AN736,但是该项目不需要。

该应用笔记可与ASM一起使用,但可以轻松移植到C。

Microchip的免费C18和XC8编译器具有I2C功能。您可以在编译器文档第2.4节中了解有关它们的更多信息。以下是一些快速入门信息:

配置

您已经具有Microchip的C18或XC8编译器。它们都具有内置的I2C功能。要使用它们,您需要包括i2c.h

#include i2c.h

如果您想看一下源代码,可以在这里找到:

  • C18接头: installation_path/vx.xx/h/i2c.h
  • C18来源: installation_path/vx.xx/src/pmc_common/i2c/
  • XC8标头: installation_path/vx.xx/include/plib/i2c.h
  • XC8来源: installation_path/vx.xx/sources/pic18/plib/i2c/

在文档中,您可以找到/i2c/函数位于文件夹中的哪个文件中。

打开连接

如果您熟悉Microchip的MSSP模块,您将知道它们首先必须初始化。您可以使用该OpenI2C功能在MSSP端口上打开I2C连接。这是它的定义方式:

void OpenI2C (unsigned char sync_mode, unsigned char slew);

使用sync_mode,您可以选择设备是主机还是从机,如果是从机,则选择使用10位还是7位地址。大多数情况下,使用7位,尤其是在小型应用程序中。选项sync_mode包括:

  • SLAVE_7 -从模式,7位地址
  • SLAVE_10 -从模式,10位地址
  • MASTER -主模式

使用slew,您可以选择设备是否应使用压摆率。有关此处内容的更多信息:I2C的压摆率是多少?

两个MSSP模块

具有两个MSSP模块的器件有一些特殊之处,例如PIC18F46K22。它们具有两组功能,一组用于模块1,另一组用于模块2。例如OpenI2C(),它们具有OpenI2C1()和来代替openI2C2()

好的,您已经全部设置好并打开了连接。现在让我们做一些例子:

例子

大师写的例子

如果您熟悉I2C协议,就会知道典型的主写序列如下所示:

Master : START | ADDR+W |     | DATA |     | DATA |     | ... | DATA |     | STOP
Slave  :       |        | ACK |      | ACK |      | ACK | ... |      | ACK |

首先,我们发送一个START条件。考虑一下拿起电话。然后,用写位的地址-拨打号码。此时,具有发送地址的从站知道自己正在被呼叫。他发送一个确认(“ Hello”)。现在,主设备可以发送数据了-他开始讲话。他发送任意数量的字节。在每个字节之后,从机应确认接收到的数据(“是的,我听到了”)。主设备完成通话后,他会挂断STOP条件。

在C语言中,母版的写入顺序对于母版如下所示:

IdleI2C();                         // Wait until the bus is idle
StartI2C();                        // Send START condition
IdleI2C();                         // Wait for the end of the START condition
WriteI2C( slave_address & 0xfe );  // Send address with R/W cleared for write
IdleI2C();                         // Wait for ACK
WriteI2C( data[0] );               // Write first byte of data
IdleI2C();                         // Wait for ACK
// ...
WriteI2C( data[n] );               // Write nth byte of data
IdleI2C();                         // Wait for ACK
StopI2C();                         // Hang up, send STOP condition

大师阅读示例

主读序列与写序列略有不同:

Master : START | ADDR+R |     |      | ACK |      | ACK | ... |      | NACK | STOP
Slave  :       |        | ACK | DATA |     | DATA |     | ... | DATA |      |

主机再次发起呼叫并拨打该号码。但是,他现在想获取信息。从机首先应答呼叫,然后开始通话(发送数据)。主机确认每个字节,直到获得足够的信息为止。然后,他发送Not-ACK并以STOP条件挂断。

在C语言中,对于主部分,它看起来像这样:

IdleI2C();                         // Wait until the bus is idle
StartI2C();                        // Send START condition
IdleI2C();                         // Wait for the end of the START condition
WriteI2C( slave_address | 0x01 );  // Send address with R/W set for read
IdleI2C();                         // Wait for ACK
data[0] = ReadI2C();               // Read first byte of data
AckI2C();                          // Send ACK
// ...
data[n] = ReadI2C();               // Read nth byte of data
NotAckI2C();                       // Send NACK
StopI2C();                         // Hang up, send STOP condition

从站代码

对于从机,最好使用中断服务程序或ISR。您可以将微控制器设置为在调用地址时接收中断。这样,您就不必经常检查公共汽车。

首先,让我们为中断设置基础。您必须启用中断并添加ISR。PIC18具有两个级别的中断很重要:高电平和低电平。我们将I2C设置为高优先级中断,因为答复I2C呼叫非常重要。我们要做的是以下几点:

  • 编写一个SSP ISR,用于该中断是一个SSP中断(而不是另一个中断)时
  • 当中断为高优先级时,编写一个通用的高优先级ISR。此功能必须检查触发了哪种中断,并调用正确的子ISR(例如SSP ISR)
  • GOTO在高优先级中断向量上向通用ISR 添加一条指令。我们不能将通用ISR直接放在向量上,因为在许多情况下它太大了。

这是一个代码示例:

// Function prototypes for the high priority ISRs
void highPriorityISR(void);

// Function prototype for the SSP ISR
void SSPISR(void);

// This is the code for at the high priority vector
#pragma code high_vector=0x08
void interrupt_at_high_vector(void) { _asm GOTO highPriorityISR _endasm }
#pragma code

// The actual high priority ISR
#pragma interrupt highPriorityISR
void highPriorityISR() {
    if (PIR1bits.SSPIF) {        // Check for SSP interrupt
        SSPISR();            // It is an SSP interrupt, call the SSP ISR
        PIR1bits.SSPIF = 0;  // Clear the interrupt flag
    }
    return;
}

// This is the actual SSP ISR
void SSPISR(void) {
    // We'll add code later on
}

下一步是在芯片初始化时启用高优先级中断。这可以通过一些简单的寄存器操作来完成:

RCONbits.IPEN = 1;          // Enable interrupt priorities
INTCON &= 0x3f;             // Globally enable interrupts
PIE1bits.SSPIE = 1;         // Enable SSP interrupt
IPR1bits.SSPIP = 1;         // Set SSP interrupt priority to high

现在,我们正在处理中断。如果您要实现此功能,我现在将对其进行检查。编写基本信息,SSPISR()以在发生SSP中断时开始使LED闪烁。

好的,您的中断工作了。现在让我们为该SSPISR()函数编写一些实际代码。但是首先是一些理论。我们区分五种不同的I2C中断类型:

  1. 主机写入,最后一个字节为地址
  2. 主机写入,最后一个字节为数据
  3. 主机读取,最后一个字节是地址
  4. 主机读取,最后一个字节为数据
  5. NACK:传输结束

您可以通过检查SSPSTAT寄存器中的位来检查自己处于什么状态。在I2C模式下,该寄存器如下(忽略未使用或无关的位):

  • 位5:D / NOT A:数据/非地址:如果最后一个字节是数据,则设置;如果最后一个字节是地址,则清除
  • 位4:P:停止位:如果最后一次发生STOP条件(没有有效操作),则置1
  • 位3:S:起始位:如果最后一次发生START条件(有一个有效的操作)则置位
  • 位2:R / NOT W:读/不写:如果操作是主读,则置位;如果操作是主写,则清零
  • 位0:BF:缓冲区已满:如果SSPBUFF寄存器中有数据,则置1;否则,清0。

有了这些数据,很容易看到如何查看I2C模块处于什么状态:

State | Operation | Last byte | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 0
------+-----------+-----------+-------+-------+-------+-------+-------
1     | M write   | address   |   0   |   0   |   1   |   0   |   1
2     | M write   | data      |   1   |   0   |   1   |   0   |   1
3     | M read    | address   |   0   |   0   |   1   |   1   |   0
4     | M read    | data      |   1   |   0   |   1   |   1   |   0
5     | none      | -         |   ?   |   ?   |   ?   |   ?   |   ?

在软件中,最好使用状态5作为默认值,这是在不满足其他状态的要求时假定的。这样,当您不知道发生了什么时您就不会回复,因为从站不会响应NACK。

无论如何,让我们看一下代码:

void SSPISR(void) {
    unsigned char temp, data;

    temp = SSPSTAT & 0x2d;
    if ((temp ^ 0x09) == 0x00) {            // 1: write operation, last byte was address
        data = ReadI2C();
        // Do something with data, or just return
    } else if ((temp ^ 0x29) == 0x00) {     // 2: write operation, last byte was data
        data = ReadI2C();
        // Do something with data, or just return
    } else if ((temp ^ 0x0c) == 0x00) {     // 3: read operation, last byte was address
        // Do something, then write something to I2C
        WriteI2C(0x00);
    } else if ((temp ^ 0x2c) == 0x00) {     // 4: read operation, last byte was data
        // Do something, then write something to I2C
        WriteI2C(0x00);
    } else {                                // 5: slave logic reset by NACK from master
        // Don't do anything, clear a buffer, reset, whatever
    }
}

您可以看到如何使用位掩码检查SSPSTAT寄存器(首先与进行“与”操作,0x2d以便我们只有有用的位),以便查看我们拥有的中断类型。

找出响应中断时必须发送或执行的操作是您的工作:这取决于您的应用程序。

参考文献

同样,我想提及Microchip编写的有关I2C的应用笔记:

  • 实施I2C从机的AN734
  • 实施I2C主器件的AN735
  • AN736建立用于环境监控的网络协议

有有关编译器库的文档编译器库文档

自己进行设置时,请在(M)SSP部分上检查芯片的数据表以进行I2C通信。我将PIC18F46K22用作主器件,将PIC18F4620用作从器件。


3

首先,我建议仅因为它是最新版本而改用XC8编译器。有可用的外设库,但我从未使用过。请访问Microchip网站以获取详细信息和文档。

好的,我这里有一些很老的基本例程,用于我很久以前与PIC16F和旧的Microhip中档编译器(它可能是高科技的)一起使用的I2C eeprom通讯,但是我认为它们可以正常工作因为我认为PIC18与PIC18相同。无论如何,您都会很快发现。
它们是用于温度记录器项目的较大文件的一部分,因此我迅速删除了所有其他不相关的功能,并保存为以下文件,因此可能我对此做了一些混乱,但是希望您将能够对所需的内容有所了解(它甚至可以工作,您永远不会知道;-))

您将需要确保主头文件是正确的,并检查/更改引脚以确保它们是正确的I2C外设引脚,以及寄存器名称(如果它们是来自Hi-Tech编译器的话),这样做的方式有些不同关于寄存器命名约定。

I2C.c文件:

#include "I2C.h"
#include "delay.h"
#include <pic.h>

#define _XTAL_FREQ 20000000


void write_ext_eeprom(unsigned int address, unsigned char data)
 {
    unsigned char a0 = ((address & 0x8000) >> 14);  
    unsigned char msb = (address >> 8);
    unsigned char lsb = (address & 0x00FF);


   i2c_start();
   i2c_write(0xa0 | a0);
   i2c_write(msb);
   i2c_write(lsb);
   i2c_write(data);
   i2c_stop();
   DelayMs(11);
}

/******************************************************************************************/

unsigned char read_ext_eeprom(unsigned int address)
{
   unsigned char a0 = ((address & 0x8000) >> 14);  
   unsigned char data;
   unsigned char msb = (address >> 8);
   unsigned char lsb = (address & 0x00FF);

   i2c_start();
   i2c_write(0xa0 | a0);
   i2c_write(msb);
   i2c_write(lsb);
   i2c_repStart();
   i2c_write(0xa1 | a0);
   data=i2c_read(0);
   i2c_stop();
   return(data);
}

void i2c_init()
{
 TRISC3=1;           // set SCL and SDA pins as inputs
 TRISC4=1;

 SSPCON = 0x38;      // set I2C master mode
 SSPCON2 = 0x00;

 //SSPADD = 9;          // 500kHz bus with 20MHz xtal 
 SSPADD = 49;           // 100kHz bus with 20Mhz xtal

 CKE=0;     // use I2C levels      worked also with '0'
 SMP=1;     // disable slew rate control  worked also with '0'

 PSPIF=0;      // clear SSPIF interrupt flag
 BCLIF=0;      // clear bus collision flag
}

/******************************************************************************************/

void i2c_waitForIdle()
{
 while (( SSPCON2 & 0x1F ) | RW ) {}; // wait for idle and not writing
}

/******************************************************************************************/

void i2c_start()
{
 i2c_waitForIdle();
 SEN=1;
}

/******************************************************************************************/

void i2c_repStart()
{
 i2c_waitForIdle();
 RSEN=1;
}

/******************************************************************************************/

void i2c_stop()
{
 i2c_waitForIdle();
 PEN=1;
}

/******************************************************************************************/

int i2c_read( unsigned char ack )
{
 unsigned char i2cReadData;

 i2c_waitForIdle();

 RCEN=1;

 i2c_waitForIdle();

 i2cReadData = SSPBUF;

 i2c_waitForIdle();

 if ( ack )
  {
  ACKDT=0;
  }
 else
  {
  ACKDT=1;
  }
  ACKEN=1;               // send acknowledge sequence

 return( i2cReadData );
}

/******************************************************************************************/

unsigned char i2c_write( unsigned char i2cWriteData )
{
 i2c_waitForIdle();
 SSPBUF = i2cWriteData;
//if(ACKSTAT)
{
//while(ACKSTAT);
}
 return ( ! ACKSTAT  ); // function returns '1' if transmission is acknowledged
}

I2C.h头文件:

extern void i2c_init();
extern void i2c_waitForIdle();
extern void i2c_start();
extern void i2c_repStart();
extern void i2c_stop();
extern int i2c_read( unsigned char ack );
extern unsigned char i2c_write( unsigned char i2cWriteData );

谢谢,这是主要部分,实际上可能与PIC18相同。也感谢您的编译器说明:-)询问了一下我一些应用程序说明,​​因此我将自己添加为答案。

您应该考虑添加有关设置波特率发生器的部分。该代码通常看起来像是SSP1ADD = ((_XTAL_FREQ/100000)/4)-1;为1KHz的,等等
杰西·克雷格

1

XC8和XC​​16编译器包含用于I2C的库。

我遇到的问题是文档不是很好!如果使用Microchip文档中的示例,那么您会很不幸。甚至Microchip支持也无法为您提供帮助。我自己去过那里。

不久前,我使用PIC24EP512GP系列单片机,Microchip记录该库对我不起作用。


啊,真可惜!那么你做了些什么?

不幸的是我即兴创作。
Chetan Bhargava 2013年

1
它们对其他人也有用吗?我想看看他们!
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.