Ich arbeite an einem Projekt, bei dem ein PIC12LF1552-Mikroprozessor I2C verwendet, um mit einem TMP75-Temperatursensor zu sprechen. Wie der Titel schon sagt, tritt immer wieder ein sehr seltsames Problem auf.
Jedes Mal, wenn ich das Mikro einschalte, bleiben SDA (Datenleitung) und SCL (Taktleitung) niedrig. Nach der Wartezeit von 18 Sekunden im Code gehen beide Leitungen hoch, wenn die I2C_init-Funktion den I2C-Bus startet. An diesem Punkt weiß ich, dass das Mikro in das Konfigurationsregister im Temperatursensor schreiben sollte. Die Endlosschleife blinkt jedes Mal, wenn das Mikro auf den Temperatursensor schreibt, eine LED.
Das Problem ist, dass die I2C-Leitungen hoch bleiben, es sei denn, ich entferne den SDA-Pullup-Widerstand und stecke ihn dann wieder ein. Sobald der SDA-Pullup wieder angeschlossen ist, erfasst das Oszilloskop eine vollkommen normale I2C-Kommunikation, und alles funktioniert einwandfrei. (Siehe unten)
Ich verwende jetzt 1k-Pullup-Widerstände, aber im obigen Screenshot habe ich 4,7k verwendet, was zu groß ist. Mit 1k-Widerständen sind die Taktleitung und die Datenleitung viel quadratischer und sehen richtiger aus. Wenn die internen schwachen Pullups auf dem Mikro aktiviert sind, würde die I2C-Kommunikation überhaupt nicht funktionieren.
Ich habe überprüft, dass jedes einzelne Bit des gesendeten I2C-Schreibbefehls korrekt ist. Ich habe auch einen Raspberry Pi verwendet, um den TMP75 erfolgreich zu lesen und zu schreiben.
Die im Mikro eingestellte I2C-Busgeschwindigkeit beträgt 100 kHz. Der TMP75 kann bis zu 400 kHz verarbeiten, das ist also kein Problem.
Das erneute Einfügen des SDA-Pullups ermöglicht die normale I2C-Kommunikation. Das erneute Einfügen des SCL-Pullups, während der I2C "hängt", behebt das Problem jedoch nicht.
Ich habe mehrere Professoren an meiner Hochschule nach diesem Problem gefragt, aber keiner von ihnen hat eine Lösung gefunden.
Meine Frage ist also: Was könnte möglicherweise dazu führen, dass der SDA-Pullup erneut eingefügt werden muss, damit der I2C mit der Kommunikation beginnen kann?
Haupt c
#include <xc.h>
#include "i2c.h"
//PIC12LF1552 Configuration Bit Settings
// 'C' source line config statements
#define _XTAL_FREQ 16000000
#define I2C_SLAVE 0x48
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
// CONFIG1
#pragma config FOSC = INTOSC // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = OFF // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = ON // Flash Program Memory Code Protection (Program memory code protection is enabled)
#pragma config BOREN = OFF // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
// CONFIG2
#pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off)
#pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LPBOR = OFF // Low-Power Brown Out Reset (Low-Power BOR is disabled)
#pragma config LVP = ON // Low-Voltage Programming Enable (Low-voltage programming enabled)
// Send a command to I2C chip
void i2c_write_command(unsigned char address, unsigned char command)
{
i2c_Start(); // Send Start
i2c_Address(I2C_SLAVE, I2C_WRITE); // Send slave address - write operation
i2c_Write(address); // Send register address
i2c_Write(command); // Send register value (command)
i2c_Stop(); // Send Stop
}
// Read a char from I2C chip
unsigned char i2c_read_command(unsigned char address)
{
unsigned char read_byte;
// Read one byte
i2c_Start(); // send Start
i2c_Address(I2C_SLAVE, I2C_WRITE); // Send slave address - write operation
i2c_Write(address); // Set register for read
i2c_Restart(); // Restart
i2c_Address(I2C_SLAVE, I2C_READ); // Send slave address - read operation
read_byte = i2c_Read(0); // Read one byte
// If more than one byte to be read, (0) should
// be on last byte only
// e.g.3 bytes= i2c_Read(1); i2c_Read(1); i2c_Read(0);
i2c_Stop(); // send Stop
return read_byte; // return byte.
// If reading more than one byte
// store in an array
}
void main(void) {
unsigned char temp1;
int i=0;
OSCCON = 0b01111010; // set internal osc to 16MHz
TRISA = 0b000000; // set ports to outputs
PORTA = 0b000000;
//OPTION_REG = 0b01011000; //or hex 0x58, enable pull up resistors
//WPUA1 = 1; //enable individual pull ups
//WPUA2 = 1;
__delay_ms(9000);
__delay_ms(9000);
i2c_Init(); // Start I2C as Master 100KHz
i2c_write_command(0x1,0x78); // Write configuration to register
//temp1 = i2c_read_command(0x0);
//__delay_ms(5000);
while(1)
{
RA4 = 1;
__delay_ms(500);
RA4 = 0;
__delay_ms(500);
i2c_write_command(0x01,0x78);
}
return;
}
#define I2C_WRITE 0
#define I2C_READ 1
// Initialise MSSP port. (12F1822 - other devices may differ)
void i2c_Init(void){
// Initialise I2C MSSP
// Master 100KHz
TRISA1=1; // set SCL and SDA pins as inputs
TRISA2=1;
SSPCON1 = 0b00101000; // I2C enabled, Master mode
SSPCON2 = 0x00; // I2C Master mode, clock = FOSC/(4 * (SSPADD + 1))
SSPADD = 39; // 100Khz @ 16Mhz Fosc
SSPSTAT = 0b11000000; // Slew rate disabled
}
// i2c_Wait - wait for I2C transfer to finish
void i2c_Wait(void)
{
while ( ( SSP1CON2 & 0x1F ) || ( SSPSTAT & 0x04 ) );
}
// i2c_Start - Start I2C communication
void i2c_Start(void)
{
//i2c_Wait();
SEN=1; //Indicates that start bit has been detected last
while(SEN);
}
// i2c_Restart - Re-Start I2C communication
void i2c_Restart(void){
//i2c_Wait();
RSEN=1; //Enables receiver mode for I2C master mode only
while(RSEN);
}
// i2c_Stop - Stop I2C communication
void i2c_Stop(void)
{
//i2c_Wait();
PEN=1; //Initiate stop condition on SDA and SCL pins, auto cleared by hardware
while(PEN);
}
// i2c_Write - Sends one byte of data
void i2c_Write(unsigned char data)
{
SSPBUF = data; //MSSP1 Receive Buffer/Transmit Register
while(BF); // wait till complete data is sent from buffer
i2c_Wait();
}
// i2c_Address - Sends Slave Address and Read/Write mode
// mode is either I2C_WRITE or I2C_READ
void i2c_Address(unsigned char address, unsigned char mode)
{
unsigned char l_address;
l_address=address<<1;
l_address+=mode;
SSPBUF = l_address;
while(BF); // wait till complete data is sent from buffer
i2c_Wait();
}
// i2c_Read - Reads a byte from Slave device
unsigned char i2c_Read(unsigned char ack)
{
// Read data from slave
// ack should be 1 if there is going to be more data read
// ack should be 0 if this is the last byte of data read
unsigned char i2cReadData;
//i2c_Wait();
RCEN=1;
while(!BF); // wait for buffer full //i2c_Wait();
i2cReadData = SSPBUF;
i2c_Wait();
if ( ack ) ACKDT=0; // Ack
else ACKDT=1; // NAck
ACKEN=1; // send acknowledge sequence
return( i2cReadData );
}
Vielen Dank für Ihre Zeit.
EDIT: i2c.h aktualisiert EDIT2: Screenshot mit 1k-Pullup-Widerständen hochgeladen.
Ihr Problem ist, dass Sie i2c_wait () -Befehle durch Ihren Code gestreut haben.
Dies ist ein Mikrocontroller, es ist einfach, es macht genau das, was Sie wollen. Der Zustand muss nicht ständig überprüft werden. Sie sollten i2c_wait() nur aufrufen müssen, nachdem Sie mit SSPBUF interagiert haben. Schauen Sie sich http://www.8051projects.net/wiki/I2C_Implementation_on_PIC an
Indem Sie SCL hoch halten und SDA umschalten (Entfernen und Ersetzen des Widerstands), senden Sie ein Startbit, gefolgt von einem Stoppbit. Es ist nicht klar, warum dies die von Ihnen überprüften Register repariert, da Chris Stratton vorgeschlagen hat, dass Sie die Register löschen könnten, wenn Sie sich wirklich darum kümmern würden.
Nun, das ist vielleicht keine vollständige Antwort, aber es löst das Problem. Die Problemumgehung für dieses Problem bestand darin, die SDA-Leitung manuell umzuschalten, nachdem ich den i2c-Bus initialisiert hatte. Hier ist der Code, den ich nach dem Aufruf von i2c_init() hinzugefügt habe.
TRISA2 = 0;
RA2 = 1;
RA2 = 0;
__delay_ms(10);
RA2 = 1;
TRISA2 = 1;
Durch manuelles Umschalten der SDA-Leitung wird das Problem behoben. Ich kann den TMP75-Temperatursensor jetzt problemlos lesen und beschreiben. Danke für die Hilfe an alle.
Aus dem Datenblatt Ihres Temperatursensors:
7.3.2.8 Timeout-Funktion Der TMP175 setzt die serielle Schnittstelle zurück, wenn entweder SCL > oder SDA für 54 ms (typisch) zwischen einem START- und STOP-Zustand niedrig gehalten wird. Der TMP175 gibt den Bus frei, wenn er auf Low gezogen wird, und wartet auf eine START-Bedingung. Um eine Aktivierung der Timeout-Funktion zu vermeiden, muss für die SCL-Betriebsfrequenz eine Kommunikationsgeschwindigkeit von mindestens 1 kHz eingehalten werden.
Vielleicht greift Ihr Sensor nach dem Bus und lässt ihn nicht los, und Ihre Pullup-Manipulation reicht aus, um ihn loszulassen.
In jedem Fall ist es an der Zeit, die Ärmel hochzukrempeln und ernsthaft zu debuggen.
Eine gute Möglichkeit, diese Art von Dingen zu debuggen, ist ein Oszilloskop mit einem erweiterten seriellen Triggermodul, sodass Sie auf ein Muster triggern können, um nach dieser Verzögerung von 54 ms zu suchen.
Alternativ gibt es an dieser Stelle kaum eine Entschuldigung dafür, nicht zu wissen, wo oder wie Ihr Programm hängt. Schalten Sie zumindest ein digitales Bit in main um, um sich davon zu überzeugen, dass die While-Schleife nicht ausgeführt wird. Sobald dies erledigt ist und Sie feststellen, dass dies nicht der Fall ist, schalten Sie Bits ein, wenn Sie in eine Subroutine eintreten, und aus, wenn Sie sie verlassen. Kurz gesagt, Sie sollten zumindest wissen, WO Sie hängen. Schalten Sie danach ein digitales Bit ein, das als Scope-Trigger für den Kommunikationsabschnitt dient, mit dem Sie Probleme haben, damit Sie ein sehr klares Bild davon bekommen, was vor sich geht.
Vielleicht noch besser, verwenden Sie Ihre Timer, um jede Kommunikations-Subroutine zu timen, und beenden Sie sie mit einem Fehlercode. Eventuell den Fehlercode auf einem DIO flashen.
Daniel
Chris Stratton
Rdd
Adam Lawrence
lod
Rdd