Связь с I2C устройствами и анализ сигналов шины с помощью цифрового ввода-вывода

Связь с инструментами и устройствами на уровне протокола, а также с физическим слоем. Используйте I2C функцию Instrument Control Toolbox для связи с TMP102 датчиком температуры и одновременно анализируйте коммуникации физического слоя I2C шины с помощью синхронизированной функции цифрового ввода-вывода Data Acquisition Toolbox.

Требуются Data Acquisition Toolbox и Instrument Control Toolbox.

Аппаратное строение и схема

  • Может использоваться любое поддерживаемое устройство National Instruments™ DAQ с синхронизируемыми каналами DIO (например, NI Elvis II)

  • TotalPhase Aardvark I2C/SPI хост-адаптер

  • TMP102 цифровой датчик температуры с двухпроводным последовательным интерфейсом

Для работы TMP102 требуется питание 3,3 В. Используйте линейный LDO (LP2950-33), чтобы сгенерировать питание 3,3 В от линии питания устройства DAQ 5 В.

Альтернативные опции включают:

  • Используйте внешний источник степени.

  • Используйте аналоговый выходной канал от устройства DAQ.

Подключитесь к датчику TMP102 с помощью I2C Host Adaptor и считайте данные о температуре

Подключите датчик и проверьте связь с ним, используя объект I2C из Instrument Control Toolbox.

aa = instrhwinfo('i2c', 'aardvark');      % Get information about connected I2C hosts
tmp102 = i2c('aardvark',0,hex2dec('48')); % Create an I2C object to connect to the TMP102
tmp102.PullupResistors = 'both';          % Use host adaptor pull-up resistors
fopen(tmp102);                            % Open the connection
data8 = fread(tmp102, 2, 'uint8');        % Read 2 byte data
% One LSB equals 0.0625 deg. C
temperature = ...
    (double(bitshift(int16(data8(1)), 4)) +...
     double(bitshift(int16(data8(2)), -4))) * 0.0625; % Refer to TMP102 data sheet to calculate temperature from received data
fprintf('The temperature recorded by the TMP102 sensor is: %s deg. C\n',num2str(temperature));
fclose(tmp102);
The temperature recorded by the TMP102 sensor is: 27.625 deg. C

Получите соответствующие I2C сигналы физического слоя с помощью устройства DAQ

Используйте цифровые каналы с избыточной дискретизацией от NI Elvis (Dev4) для приобретения и анализа коммуникаций физического слоя на I2C шине.

Получите данные SDA на порте 0, линия 0 вашего устройства DAQ. Получите данные SCL на порте 0, линия 1 вашего устройства DAQ.

dd = daq("ni");
addinput(dd,"Dev4","port0\line0","Digital"); % sda
addinput(dd,"Dev4","port0\line1","Digital"); % scl

Сгенерируйте тактовый сигнал для использования с цифровой подсистемой

Цифровые подсистемы на устройствах NI DAQ не имеют собственных часов; они должны совместно использовать часы с аналоговой подсистемой или импортировать часы из внешней подсистемы. Сгенерируйте часы заполнения 50% на 1 МГц с помощью PulseGeneration выход, и установите входную скорость скана так, чтобы она совпадала.

pgChan = addoutput(dd,"Dev4","ctr1"),"PulseGeneration");
dd.Rate = 1e6;
pgChan.Frequency = dd.Rate;

Часы генерируются на контакте pgChan.Terminal, что позволяет синхронизировать с другими устройствами и просматривать часы на осциллографе. Выходной импульсный сигнал счетчика импортируется как синхроимпульс.

disp(pgChan.Terminal);
addclock(dd,"ScanClock","External",["Dev4/" pgChan.Terminal]);
PFI13

Получите I2C сигналы с использованием синхронизированных цифровых каналов

Получите данные в фоновом режиме от цифровых линий SDA и SCL.

  • Запустите DataAcquisition в фоновом режиме

  • Запустите I2C операции

  • Остановите DataAcquisition после завершения I2C операций

start(dd, "continuous");
fopen(tmp102);
data8 = fread(tmp102, 2, "uint8");
% One LSB equals 0.0625 deg. C
temperature = (double(bitshift(int16(data8(1)), 4)) +...
    double(bitshift(int16(data8(2)), -4))) * 0.0625;
fclose(tmp102);
pause(0.1);
stop(dd);
myData = read(dd, "all");
Warning: Triggers and Clocks will not affect counter output channels. 

Постройте график необработанных данных, чтобы увидеть полученные сигналы. Заметьте, что линии удерживаются высоко в периоды простоя. В следующем разделе показано, как найти биты условия запуска/остановки и использовать их для изоляции областей интереса в I2C коммуникации.

figure("Name", "Raw Data");
subplot(2,1,1);

plot(myData(:,1));
ylim([-0.2, 1.2]);
ax = gca;
ax.YTick = [0,1];
ax.YTickLabel = {'Low','High'};
title("Serial Data (SDA)");
subplot(2,1,2);
plot(myData(:,2));
ylim([-0.2, 1.2]);
ax = gca;
ax.YTick = [0,1];
ax.YTickLabel = {'Low','High'};
title("Serial Clock (SCL)");

Анализируйте I2C шины физического слоя

Извлечение I2C сигналов физического слоя на линиях SDA и SCL.

sda = myData(:,1)';
scl = myData(:,2)';

Найти все поднимающиеся и падающие ребра часов.

sclFlips = xor(scl(1:end-1), scl(2:end));
sclFlips = [1 sclFlips 1];
sclFlipIndexes = find(sclFlips==1);

Вычислим тактовые периоды из тактовых индексов

sclFlipPeriods = sclFlipIndexes(1:end)-[1 sclFlipIndexes(1:end-1)];

Посредством осмотра, заметьте, что периоды простоя имеют высокий SCL для более чем 100 нас. Поскольку скорость скана = 1MS/s, каждая выборка представляет 1 нас. idlePeriodIndices указать периоды деятельности в рамках I2C коммуникации.

idlePeriodIndices = find(sclFlipPeriods>100);

Масштабируйте первый период деятельности на I2C шине. Для удобства просмотра включите 30 выборки холостой активности в переднюю и конечную части каждого графика.

range1 = sclFlipIndexes(idlePeriodIndices(1)) - 30 : sclFlipIndexes(idlePeriodIndices(2) - 1) + 30;
figure("Name", "I2C Communication Data");
subplot(2,1,1);
plot(sda(range1));
ylim([-0.2, 1.2]);
ax = gca;
ax.YTick = [0,1];
ax.YTickLabel = {'Low','High'};
title("Serial Data (SDA)");
subplot(2,1,2);
plot(scl(range1));
ylim([-0.2, 1.2]);
ax = gca;
ax.YTick = [0,1];
ax.YTickLabel = {'Low','High'};
title("Serial Clock (SCL)");

Анализируйте метрики эффективности шины

В качестве простого примера анализируйте метрики начального и остановочного условий и I2C вычисление скорости передачи битов.

  • Длительность начального условия определяется как время, необходимое для того, чтобы SCL достиг низкого уровня после того, как SDA становится низким.

  • Длительность условия остановки определяется как время, необходимое для повышения SDA после того, как SCL становится высоким.

  • Скорость передачи битов вычисляется путем взятия обратной величины времени между 2 возрастающими ребрами синхроимпульса.

Начальное условие: Сначала SDA низкий, затем SCL низкий

sclLowIndex = sclFlipIndexes(idlePeriodIndices(1));
sdaLowIndex = find(sda(1:sclLowIndex)==1, 1, "last") + 1; % +1, flip is next value after last high
startConditionDuration = (sclLowIndex - sdaLowIndex) * 1/s.Rate;

fprintf('sda: %s\n', sprintf('%d ', sda(sdaLowIndex-1:sclLowIndex))); % Indexes point to next change, hence sclLowIndex includes flip to low
fprintf('scl: %s\n', sprintf('%d ', scl(sdaLowIndex-1:sclLowIndex))); % subtract 1 from sdaLowIndex to see sda value prior to flip
fprintf('Start condition duration: %d sec.\n\n', startConditionDuration); % count 5 pulses, 5 us.
sda: 1 0 0 0 0 0 0 
scl: 1 1 1 1 1 1 0 
Start condition duration: 5.000000e-06 sec.

Остановите условие: сначала SCL высокий, затем SDA высокий

% flip prior to going into idle is the one we want
sclHighIndex = sclFlipIndexes(idlePeriodIndices(2)-1);
sdaHighIndex = find(sda(sclHighIndex:end)==1, 1, 'first') + sclHighIndex - 1;
stopConditionDuration = (sdaHighIndex - sclHighIndex) * 1/s.Rate;

fprintf('sda: %s\n', sprintf('%d ',sda(sclHighIndex-1:sdaHighIndex)));
fprintf('scl: %s\n', sprintf('%d ',scl(sclHighIndex-1:sdaHighIndex)));
fprintf('Stop condition duration: %d sec.\n\n', stopConditionDuration);
sda: 0 0 0 0 0 0 1 
scl: 0 1 1 1 1 1 1 
Stop condition duration: 5.000000e-06 sec.

Скорость передачи битов: Обратное время между 2 восходящими ребрами на линии SCL

startConditionIndex = idlePeriodIndices(1);
firstRisingClockIndex = startConditionIndex + 2;
secondRisingClockIndex = firstRisingClockIndex + 2;
clockPeriodInSamples = sclFlipIndexes(secondRisingClockIndex) - sclFlipIndexes(firstRisingClockIndex);
clockPeriodInSeconds = clockPeriodInSamples * 1/s.Rate;
bitRate = 1/clockPeriodInSeconds;

fprintf('DAQ calculated bit rate = %d; Actual I2C object bit rate = %dKHz\n', ...
    bitRate,...
    tmp102.BitRate);
DAQ calculated bit rate = 1.000000e+05; Actual I2C object bit rate = 100KHz

Найдите битовый поток путем дискретизации на восходящих ребрах

The sclFlipIndexes вектор был создан с использованием XOR и, следовательно, содержит как восходящие, так и падающие ребра. Начните с поднимающегося ребра и используйте шаг из двух, чтобы пропустить падающие ребра.

% idlePeriodIndices(1)+1 is first rising clock edge after start condition.
% Use a step of two to skip falling edges and only look at rising edges.
% idlePeriodIndices(2)-1 is the index of the rising edge of the stop condition.
% idlePeriodIndices(2)-3 is the last rising clock edge in the bit stream to be
% decoded.
bitStream = sda(sclFlipIndexes(idlePeriodIndices(1)+1:2:idlePeriodIndices(2)-3));
fprintf('Raw bit stream extracted from I2C physical layer signal: %s\n\n', sprintf('%d ', bitStream));
Raw bit stream extracted from I2C physical layer signal: 1 0 0 1 0 0 0 1 0 0 0 0 1 1 0 1 1 0 1 0 1 0 0 0 0 0 1 

Декодируйте полученный битовый поток

ADR_RW = {'W', 'R'};
ACK_NACK = {'ACK', 'NACK'};
address = bitStream(1:7); % 7 bit address
fprintf('\nDecoded Address: %d%d%d%d%d%d%d(0x%s) %d(%s) %d(%s)\n', ...
    address,...
    binaryVectorToHex(address),...
    bitStream(8),...
    ADR_RW{bitStream(8)+1},...
    bitStream(9),...
    ACK_NACK{bitStream(9)+1});
for iData = 0:1
    startBit = 10 + iData*9;
    endBit = startBit + 7;
    ackBit = endBit + 1;
    data = bitStream(startBit:endBit);
    fprintf('Decoded Data%d: %s(0x%s) %d(%s)\n', ...
        iData+1,...
        sprintf('%d', data),...
        binaryVectorToHex(data),...
        bitStream(ackBit),...
        ACK_NACK{bitStream(ackBit)+1});
end
Decoded Address: 1001000(0x48) 1(R) 0(ACK)
Decoded Data1: 00011011(0x1B) 0(ACK)
Decoded Data2: 10100000(0xA0) 1(NACK)

Проверьте, что декодированные данные с использованием DAQ совпадают с данными, считанными с помощью ИКТ

Два uint8 считывались байты, используя fread, от I2C шины в переменную data8. Шестнадцатеричное преобразование этих значений должно совпадать с результатами декодирования шины, показанного выше.

fprintf('Data acquired from I2C object: 0x%s\n', dec2hex(data8)');
fprintf('Temperature: %2.2f deg. C\n\n', temperature);
Data acquired from I2C object: 0x1BA0
Temperature: 27.63 deg. C