Связь с инструментами и устройствами на уровне протокола, а также с физическим слоем. Используйте 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.
Подключите датчик и проверьте связь с ним, используя объект 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
Используйте цифровые каналы с избыточной дискретизацией от 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
Получите данные в фоновом режиме от цифровых линий 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 сигналов физического слоя на линиях 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)
Два 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