ponyprog/SrcPony/i2cbus.cpp
Claudio Lanconelli 1ea8c14e75 fix year
2020-02-05 17:23:29 +01:00

511 lines
8.9 KiB
C++

//=========================================================================//
// //
// PonyProg - Serial Device Programmer //
// //
// Copyright (C) 1997-2020 Claudio Lanconelli //
// //
// http://ponyprog.sourceforge.net //
// //
//-------------------------------------------------------------------------//
// //
// This program is free software; you can redistribute it and/or //
// modify it under the terms of the GNU General Public License //
// as published by the Free Software Foundation; either version2 of //
// the License, or (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
// General Public License for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program (see LICENSE); if not, write to the //
// Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. //
// //
//=========================================================================//
#include "types.h"
#include "globals.h"
#include "e2profil.h"
#include "i2cbus.h"
#include "errcode.h"
#include "e2cmdw.h"
#include <QDebug>
#include <QtCore>
#define BUSYDELAY 100
#define SDATIMEOUT 200
I2CBus::I2CBus(BusInterface *ptr)
: BusIO(ptr)
{
}
I2CBus::~I2CBus()
{
Close();
}
int I2CBus::CheckBusy()
{
if (busI->CheckDataLines(BUSYDELAY, 1, 1))
{
return OK;
}
else
{
return IICERR_BUSBUSY;
}
}
/** I2CBus specification **
-- Standard I2CBus --
SCL
T-low = 5 usec min
T-high = 4 usec min
SCL
Data setup time = 300 nsec min
Data hold time = 300 nsec min
ALL
fall time = 300 nsec max
rise time = 1 usec max
-- Fast I2CBus --
SCL
T-low = 1.3 usec min
T-high = 0.6 usec min
ALL
fall time = 300 nsec max
rise time = 300 nsec max
**/
int I2CBus::SendStart()
{
register int err;
setSDA();
ShotDelay(2); //Help repeated start condition
setSCL();
#ifdef SCLTIMEOUT
if (getSCL() == 0)
{
int k;
for (k = SCLTIMEOUT; getSCL() == 0 && k > 0; k--)
{
WaitUsec(1);
}
if (k == 0)
{
return IICERR_SCLCONFLICT;
}
}
#endif
ShotDelay(2);
int recoveryCnt = 0;
while (CheckBusy() != OK && recoveryCnt++ < 3)
{
//Recover I2C
RecoverSlave();
}
if ((err = CheckBusy()) != OK)
{
return err;
}
clearSDA();
ShotDelay(4); // tHD;STA = 4 usec
clearSCL();
ShotDelay();
return OK;
}
int I2CBus::SendStop()
{
clearSCL();
ShotDelay(2);
clearSDA();
ShotDelay(2);
setSCL();
#ifdef SCLTIMEOUT
if (getSCL() == 0)
{
int k;
for (k = SCLTIMEOUT; getSCL() == 0 && k > 0; k--)
{
WaitUsec(1);
}
if (k == 0)
{
qWarning() << Q_FUNC_INFO << "*** SCL error";
return IICERR_SCLCONFLICT;
}
}
#else
ShotDelay();
#endif
ShotDelay(5); // tSU;STOP = 4.7 usec
setSDA();
ShotDelay(2);
if (getSDA() == 0)
{
qWarning() << Q_FUNC_INFO << "*** SDA error";
return IICERR_SDACONFLICT;
}
//tBUF = 4.7 usec
ShotDelay(2);
return OK;
}
int I2CBus::SendBitMast(int inbit)
{
int err = OK;
busI->xferBit(err, inbit, xMODE_WRONLY);
return err;
}
int I2CBus::RecBitMast()
{
int err = OK;
setSDA();
int rv = busI->xferBit(err, 1, xMODE_RDONLY);
if (err == OK)
{
return rv;
}
else
{
return err;
}
}
void I2CBus::RecoverSlave()
{
int k;
for (k = 0; k < 9; k++)
{
SendBitMast(1);
if (getSDA() != 0)
{
break;
}
}
setSCL();
ShotDelay();
}
int I2CBus::WriteByte(int by, bool lsb)
{
int err = OK;
busI->xferByte(err, by, xMODE_WRONLY, 8, lsb);
if (err == OK)
{
//Receive Ack
setSDA();
int lrb = busI->xferBit(err, 1, xMODE_RDONLY);
if (err == OK)
{
if (lrb)
{
err = IICERR_NOTACK;
}
}
}
return err;
}
int I2CBus::ReadByte(int ack, bool lsb)
{
int err = OK;
setSDA();
int rv = busI->xferByte(err, 0xff, xMODE_RDONLY, 8, lsb);
if (err == OK)
{
//Send Ack
busI->xferBit(err, ack, xMODE_WRONLY);
}
if (err == OK)
{
return rv;
}
else
{
return err;
}
}
void I2CBus::SetDelay()
{
int val = E2Profile::GetI2CSpeed();
int n;
switch (val)
{
case TURBO:
n = 1; // as fast as your PC can (dangerous)
break;
case FAST:
n = 2; // > 100 Khz, < 400 Khz
break;
case SLOW:
n = 20; // (< 25 Khz)
break;
case VERYSLOW:
n = 80;
break;
case ULTRASLOW:
n = 500;
break;
default:
n = 5; //Default (< 100KHz)
break;
}
Q_CHECK_PTR(busI);
busI->SetDelay(n);
qDebug() << Q_FUNC_INFO << "=" << n;
}
long I2CBus::Read(int slave, uint8_t *data, long length, int page_size)
{
long len;
//qDebug() << Q_FUNC_INFO << "(" << (hex) << slave << "," << (void *)data << "," << (dec) << length << ") - IN";
len = StartRead(slave, data, length);
if (len == length)
if (err_no || Stop())
{
len = 0;
}
//qDebug() << Q_FUNC_INFO << "=" << len << ", err_no =" << err_no << " - OUT";
return len;
}
long I2CBus::Write(int slave, uint8_t const *data, long length, int page_size)
{
long len;
//qDebug() << Q_FUNC_INFO << "(" << (hex) << slave << "," << data << "," << (dec) << length << ") - IN";
len = StartWrite(slave, data, length);
if (len == length)
if (err_no || Stop())
{
len = 0;
}
//qDebug() << Q_FUNC_INFO << "=" << len << ", err_no =" << err_no << " - OUT";
return len;
}
int I2CBus::Start(uint8_t slave)
{
int temp;
// send Start
if ((temp = SendStart()))
{
err_no = temp;
return err_no;
}
if ((temp = WriteByte(slave)) != OK)
{
err_no = (temp == IICERR_NOTACK) ? IICERR_NOADDRACK : temp;
last_addr = slave;
return err_no;
}
return OK;
}
/* se length e` zero, viene ricevuto un byte senza ack per cercare di
* liberare il bus da uno slave che per qualche motivo e` ancora in
* trasmissione. In questo caso data deve puntare ad un buffer di (almeno)
* un byte.
*/
long I2CBus::StartRead(uint8_t slave, uint8_t *data, long length)
{
int temp;
long len = length;
//qDebug() << Q_FUNC_INFO << "(" << (hex) << slave << "," << data << "," << (dec) << length << ") - IN";
if (len > 0)
{
// send Start
if ((temp = SendStart()))
{
err_no = temp;
return 0;
}
if ((temp = WriteByte(slave | 1)) != 0)
{
err_no = (temp == IICERR_NOTACK) ? IICERR_NOADDRACK : temp;
last_addr = slave | 1;
return 0;
}
while (len > 1)
{
if ((temp = ReadByte(0)) < 0)
{
err_no = temp;
goto fineR;
}
*data++ = (uint8_t)temp;
len--;
}
}
// last byte received without acknowledge
if ((temp = ReadByte(1)) < 0)
{
err_no = temp;
goto fineR;
}
len--;
*data = (uint8_t)temp;
err_no = 0;
fineR:
//qDebug() << Q_FUNC_INFO << "=" << (long)(length - len) << ", err_no =" << err_no << " - OUT";
return length - len;
}
long I2CBus::StartWrite(uint8_t slave, uint8_t const *data, long length)
{
int error;
long len = length;
//qDebug() << Q_FUNC_INFO << "(" << (hex) << slave << "," << data << "," << (dec) << length << ") - IN";
if (len == 0)
{
return 0;
}
if ((error = SendStart()))
{
err_no = error;
return 0;
}
if ((error = WriteByte(slave & 0xFE)))
{
err_no = (error == IICERR_NOTACK) ? IICERR_NOADDRACK : error;
last_addr = slave & 0xFE;
return 0;
}
while (len > 0)
{
if ((error = WriteByte(*data++)) != 0)
{
err_no = error;
goto fineW;
}
len--;
}
fineW:
//qDebug() << Q_FUNC_INFO << "=" << (long)(length - len) << ", err_no =" << err_no << " - OUT";
return length - len;
}
int I2CBus::Stop(void)
{
//qDebug() << Q_FUNC_INFO << "- IN";
err_no = SendStop() ? IICERR_STOP : 0;
//qDebug() << Q_FUNC_INFO << "=" << err_no << "- OUT";
return err_no;
}
int I2CBus::Reset(void)
{
qDebug() << Q_FUNC_INFO << "- IN";
SetDelay();
uint8_t c;
Read(0x00, &c, 0);
setSCLSDA();
WaitMsec(100); //a big delay to allow no-CMOS 2402 to work
qDebug() << Q_FUNC_INFO << "- OUT";
return OK;
}
void I2CBus::Close(void)
{
qDebug() << Q_FUNC_INFO << "busI=" << (hex) << busI << (dec);
setSCLSDA();
BusIO::Close();
}
int I2CBus::TestPort(int port)
{
int ret_val = busI->TestSave(port);
if (ret_val == OK)
{
// Send general call address 0x00
int err = Start(0x00 | 1);
if (err != OK && err != IICERR_NOADDRACK)
{
ret_val = err;
}
}
busI->TestRestore();
return ret_val;
}