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.
Соедините датчик и проверьте, что можно связаться с ним с помощью объекта 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. В нашей настройке 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
Установите сеанс запускаться в непрерывном режиме, пока не остановлено.
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 на 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)
Два байта 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