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

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

Этот пример требует Data Acquisition Toolbox™ и Instrument Control Toolbox™

Аппаратная конфигурация и схематичный

  • Поддерживаемое Национальное устройство DAQ любого основанного на сеансе Интерфейса Instruments™ с синхронизированными каналами DIO может использоваться (например, NI Элвис II)

  • Муравьед TotalPhase Хост-адаптер I2C/SPI

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

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

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

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

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

Свяжите с датчиком TMP102 с помощью хост-адаптера I2C и считайте температурные данные

Соедините датчик и проверьте, что можно связаться с ним с помощью объекта 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

Используйте сверхдискретизированный, синхронизировал цифровые каналы, чтобы получить и анализировать коммуникации физического уровня на шине I2C. В нашей настройке NI у Элвиса II был ID Устройства Dev4.

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

s = daq.createSession('ni');
addDigitalChannel(s,'Dev4', 'port0\line0', 'InputOnly'); % sda
addDigitalChannel(s,'Dev4', 'port0\line1', 'InputOnly'); % scl

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

Цифровые подсистемы на устройствах DAQ NI не имеют своих собственных часов; они должны совместно использовать часы с аналоговой подсистемой или получить часы от внешней подсистемы. В этом примере мы будем использовать Встречный Выходной канал Генерации Импульса, чтобы сгенерировать 50%-е часы рабочего цикла на частоте 1 000 000 Гц и установить уровень сеанса соответствовать.

pgChan = addCounterOutputChannel(s,'Dev4', 1, 'PulseGeneration');
s.Rate = 1e6;
pgChan.Frequency = s.Rate;

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

disp(pgChan.Terminal);
addClockConnection(s,'External',['Dev4/' pgChan.Terminal],'ScanClock');
PFI13

Добавьте прослушиватель объекта сеанса.

Создайте прослушиватель, чтобы собрать полученные данные к глобальной переменной myData в то время как сеанс запускается в фоновом режиме.

type saveData.m

global myData;
addlistener(s,'DataAvailable', @saveData);
function saveData(src, evt)
    global myData
    myData = [myData; evt.Data];
end


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

Установите сеанс запускаться в непрерывном режиме, пока не остановлено.

s.IsContinuous = true;

Запустите сеанс в фоновом режиме, накапливающем результаты в глобальной переменной 'myData'. Накопите полученные данные с функцией обратного вызова, которая просто добавляет полученные данные к основанной на столбце глобальной переменной типа массив.

Запустите сеанс в фоновом режиме, накапливающем полученные данные по SDA и цифровым линиям SCL к глобальной переменной myData.

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

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

  • Остановите сеанс, когда сделано

myData = [];
s.startBackground;
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);
s.stop();
Warning: Trigger and Clock Connections 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 высоко для дольше, чем 100us. Начиная с уровня = 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

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

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 использования, совпадают с данными, мы читаем ICT использования

Два 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