Jump to content

    

Arlleex

Свой
  • Content Count

    2638
  • Joined

  • Last visited

Community Reputation

0 Обычный

2 Followers

About Arlleex

  • Rank
    Гуру

Контакты

  • Сайт
    Array
  • ICQ
    Array

Recent Profile Visitors

9279 profile views
  1. У меня тоже (вообще случайно обнаружил) из драфтсмана пропал один парт, из-за чего их бы на сборочном вообще не обнаружили... Причем если кликнуть - он выделяется, но контур (как у других) не отрисовывается. Пришлось локально для него в опциях отображение по другим механическим слоям ставить. Пока что спихнул на кривую модель микросхемы, хотя маловероятно.
  2. Ну, это если я буду вести разработку ПО под железку... Сейчас я временно ориентирован в PCB-дизайн.
  3. В случае, если обнаружено залипание шины - думаю, защитные стопы. В обычных транзакция - обычные стопы.
  4. Сама транзакция будет "собираться" по кирпичикам так, как указано в спецификации на конкретную микросхему слейва. 9 циклов SCL - это "не о том" песня. Это когда между мастером и слейвом произошел рассинхрон и шина "залипла" в силу смещенного положения ACK. В микросхемах с внутренним автоинкрементом адреса чтения/записи такое "проклокивание" может не прокатить. И лучше подавать СТОП-ы (вместо 9 клоков 9 СТОП-ов).
  5. Вы CSI-2 гоните? Нормально так Какие скорости по лэйнам?
  6. ИМХО, нужно всегда отдавать себе отчет, что если хочется полноценные I2C-скорости иметь, то нужно как-то на аппаратный I2C сильнее засматриваться... Но когда такой возможности нет, с более медленными скоростями смириться еще можно. С другой стороны, можно подумать еще о связке TIM + DMA -> GPIO, если такое можно провернуть в используемом МК. Или другие механизмы ногодрыга активно использовать - в LPC43xx (вроде) есть некий SGPIO, который, насколько я знаю, много чего может наногодрыжить. А на NOP-ах тоже далеко не уедешь - 100кбит/с при тактовой CPU в 32МГц (у меня сейчас так) - это уже 320 тактов в пустую. Мне скорость не особо важна, главное, что обмены со слейвом - штука регулярная. Точно, спасибо Я подумаю, как. Я обычно для своих каких-то программных механизмов всегда рисую похожие картинки, так потом проще вспомнить основную суть, не открывая исходников Слейв даже не хочу думать пока что... Нет надобности.
  7. Приветствую! А почему бы, собственно, и да. Короче, есть в каком-то обозримом будущем вероятность того, что ногодрыгом придется поднимать I2C-мастера. Глянул что вообще имеется из готового - расстроился. Часто вижу какие-то нелепые задержки на NOP-ах процессора и другой костыльный бред А даже если и так - все слишком упрощено и работает только у того, кто сие изобрел. Чуть шаг влево - например, слейв требует обязательного СТОП-СТАРТ- вместо РЕСТАРТ-условия - все, не работает. Мастер - штука довольно простая (хотя и со своими тараканами), поэтому моя идея заключается в следующем. Прикладное ПО оперирует функциями I2CSend() и I2CRecv(). Ему по-барабану на внутреннее устройство этих функций. Но в реализации софт-I2C эти функции как конструктор собирают нужную транзакцию из более мелких "простейших" операций на шине. Сами операции потактно формируются либо в прерывании таймера, либо в каком-то периодическом процессе. Предлагаю их рассмотреть. Цифрами 1, 2 ... 5 обозначаю "тики" (минимальные интервалы) таймера, штрих-линиями - границу операции, точками - проверку на обязательное соответствие логических уровней на SCL/SDA уровням, показанным на рисунке. 1. СТАРТ-условие СТАРТ-условие всегда должно начинаться с проверки, что SCL/SDA в лог. 1, иначе это будет что угодно, но не СТАРТ-условие. 2. РЕСТАРТ-условие РЕСТАРТ-условие всегда должно начинаться с проверки, что SCL в лог. 0, иначе это будет что угодно, но не РЕСТАРТ-условие. В этом случае переключаем SDA в лог. 1 вне зависимости от того, какой уровень был на ней до этого. Как видно, здесь требуется уже 4 такта. 3. БИТ Это интервал передачи/приема бита. Этот же паттерн отражает и ACK/NACK. Вначале проверяется, что SCL в лог. 0. Потом выставляем данные и "клокаем". 4. СТОП-условие СТОП-условие всегда должно начинаться с проверки, что SCL в лог. 0. Если это так, SDA опускается в лог. 0, а затем формируется СТОП-условие. 5. ЗАЩИТНОЕ СТОП-условие В отличие от простого СТОП-условия, формирует СТОП-условие из любого состояния SDA/SCL. ЗАЩИТНОЕ СТОП-условие предназначено для вывода некоторых слейвов из коматозного состояния. Хотя спецификация говорит о минимум 9 циклах на SCL, однажды мы выяснили, что лучше подавать СТОП-условия. Обычные СТОП-ы "как есть" сформировать не получится друг за другом, поэтому нужна диаграмма именно ЗАЩИТНОГО СТОП-а. Любое несоответствие на диаграммах выше (например, SCL в лог. 0 в начале СТАРТ-услвоия) - ошибка транзакции. Пример диаграммы передачи Как видно, СТАРТ можно сформировать не из любого места, а только после СТОП-а или ЗАЩИТНОГО СТОП-а. БИТ можно сформировать только между СТАРТ-ом и СТОП-ом или ЗАЩИТНЫМ СТОП-ом. ЗАЩИТНЫЕ СТОП-ы можно формировать где угодно. Собственно, прикладное ПО может сформировать последовательность из таких отрезков, чтобы получить нужную транзакцию. Однако, некоторые операции можно сократить, например, передачу/прием байта не нужно разбивать на 8 (+1 ACK) отдельных подкоманд формирования битов; простейшую транзакцию СТАРТ-передача-СТОП тоже желательно обозначить одной операцией. Т.е. не до маразма доходить. Прикладное ПО, когда хочет совершить транзакцию на шине, добавляет в очередь транзакций специального вида дескрипторы (и данные на передачу при необходимости) и запускает таймер. Таймерное прерывание потихоньку выгребает "заявки" и рисует диаграммы, шевеля ногами SCL/SDA. С приемом разберемся чуть позже, а пока рассмотрим структуру дескриптора (и данных, если прикладной код хочет что-то записать в слейв) Bus Operation ID - это некий идентификатор, по которому можно сгруппировать части одной транзакции. Например, для EEPROM же нужно сначала записать внутренний адрес ячейки памяти, и лишь потом делать транзакции чтения. Общая транзакция разбивается на 2 - первая СТАРТ + запись адреса ведомого + запись внутреннего адреса, вторая - РЕСТАРТ + запись адреса ведомого + БИТ-ы чтения (с выдачей ACK-ов) + СТОП на конце. Технически для EEPROM это одна операция, но для нас это будет 2, но под одним Bus Operation ID. Rx/Tx Byte Quantity - если это транзакция передачи (см. позже описание Bus Operation Control), то в этой же очереди следом будут идти байты, которые нужно отправить ведомому. Если это прием - данных нет, и принимаемые данные будут помещаться в очередь приема (со своими статусными сообщениями в дескрипторе) - мол, успешно или нет. Bus Operation Control - самое интересное в дескрипторе. Бит 0 указывает, что за транзакция совершается (прием или передача). В битах 2:1 указывается, нужно ли формировать СТАРТ- или РЕСТАРТ- перед "телом" транзакции. Кроме того, можно здесь указать, что шину нужно "прочистить" (подав 10 ЗАЩИТНЫХ СТОП-ов), и лишь потом выдать СТАРТ-условие. Биты 4:3 указывают, нужно ли в конце "тела" транзакции делать СТОП-условие или его разновидности. Бит 5 нужен только для транзакции приема, он указывает, нужно ли отвечать NACK-ом на последнем принимаемом байте, или ACK-ом (обычно, все-таки, должен быть NACK, но пусть будет универсально). В битах 7:6 отражается необходимость "рапортовать" о результатах транзакции (в очередь приемных сообщений) - например, при передаче сообщать, что слейв не может сожрать какой-то байт, или не получилось СТАРТ-условие сформировать и т.д. Можно настроить, что рапортация будет только при ошибках, или наоборот (только когда транзакция завершена успешно) - это удобно для реализации функции поиска девайсов на шине с заранее не известными физическими адресами. Бит 8 позволяет "прочистить" очередь запросов для транзакций с одинковыми ID по списку (если EEPROM не ответила ACK-ом на свой адрес - зачем ей передавать данные?). Бит 9 позволяет автомат "попросить" сформировать последовательность "прочистки" шины в случае, если транзакция по какой-то причине запорота. В итоге - формируем нужную последовательность, записываем в очередь передачи - она отрабатывается и рапортует в очередь приема. Что не учел? Мультимастер?
  8. Получается, что так... SLIP я привел просто как пример сейчас. Между делом мне предстоит (скорее всего) написать софт-реализацию I2C-мастера (ногодрыг), и я хочу несколько универсализировать возможности этого мастера. Хочу сделать очередь простейших операций (например, формирование СТАРТ-условия, СТАРТ + отправка N данных + СТОП и т.д.), которую будет разгребать и отрабатывать обычное прерывание по таймеру (или какой-то периодический процесс). Внутри прерывания будет формироваться нужная ногодрыжная последовательность в зависимости от того, что сейчас нужно сделать на шине. В том числе по единому указанию в очереди команд производить "очистку" шины путем подачи 18 СТОП-условий. Да много чего еще. Вот и думаю, отделять дескрипторы команд от самих данных или в ту же кучу валить.
  9. Указатель записи двигается в конце каждой функции Write(). Для универсальности и законченности, что ли... Если не двигать, а сделать отдельный метод WriteUpd(), например, там да, буфер не нужен (собственно, об этом Вы и говорите). Постараюсь однозначно сейчас определить некоторые термины, чтобы мы понимали друг друга и не путались (да и сам я не путался). У девайса есть UART - девайс может передавать и принимать данные в SLIP-кадрах. Внутри ПО организовано 2 очереди: на передачу и на прием. Из клиентского ПО я хочу оперировать "сообщениями" - массивом байт известной длины. Сообщение A добавляется в очередь на передачу с помощью функции SendMsg(A, sizeof(A)). Механизм фрейминга вычитывает из этой очереди сообщение (он их отделяет по дескриптору-длине), формирует-отправляет потихонечку кадр SLIP, назовем его A'. Теперь этот упакованный A' улетел из девайса. Что мы здесь знаем? Знаем, что клиентский код всегда в курсе, сколько он сейчас хочет отправить (sizeof(A)). Фреймер, реализованный как механизм, внедренный в ISR передачи UART-модуля, постепенно разгребает передающую очередь, обнаруживает в нем дескриптор, и отправляет один за одним байты данных из этой очереди в количестве, указанном в дескрипторе. Если UART слишком быстр (или кто-то прервал поток, который пишет в очередь очередное передаваемое сообщение, где-то в середине), передача "приостановится", но как только SendMsg() доотработает, этот механизм возобновится. С этим вопросов нет. Однако есть еще очередь принимаемых сообщений. На входе фреймера у нас A' (т.е. запакованное сообщение), и оно подается не сразу, а байт за байтом. А в очереди принимаемых сообщений с точки зрения клиентского потока я хочу видеть последовательность "дескриптор1-данные1", "дескриптор2-данные2" и т.д. Фреймер, обнаружив спецсимвол начала кадра, будет постепенно заполнять очередь по 1 байту распакованными данными (по мере приема сообщения). Но мы же не знаем, сколько нам придет? Поэтому "резервируем" местечко в очереди (по сути делаем Write() с обнулением выделенной области и запоминанием адресов реальных ячеек памяти), принимаем-распаковываем сообщение в очередь, и как только нашли конец-границу кадра - пишем в это резервированное место полученную длину. В итоге в очереди принимаемых сообщений у нас будет наше сообщение A с дескриптором sizeof(A) в начале. А уже дальше можно проверить и контрольные суммы, и границы допустимого размера сообщения, и т.д. Фреймер, кстати, при нехватке места в приемной очереди, "откатит" ее назад, и вернется в состояние "жду границу кадра/не синхронизирован", откидывая сообщение.
  10. Нет-нет, именно "если в очереди есть место под размер дескриптора + кол-во данных, указаном в этом дескрипторе"...
  11. Я наверное не совсем правильно описал: у меня один читатель, один писатель. Пишу из потока, читаю из прерывания (чтобы потом отправить вычитанный байт, например). Указатель да, передвигается после записи элементов в очередь. Но чтобы в таком случае записать дескриптор + данные, нужно делать промежуточный буфер, в котором сначала поместить дескриптор, потом скопировать данные, переданные в эту функцию по указателю, чего в одном моем проекте сделать не получится в силу ограниченного ОЗУ. Поэтому функция SendMsg() последовательными записями добавляет в очередь дескриптор, а затем сами данные u32 SendMsg(u8 msg[], u32 len) { u32 reterr = 0; if(len > 0 && DSCTxQ.GetFree() >= sizeof(len) + len) DSCTxQ.Write((RBTYPE *)&len, sizeof(len)), // запись дескриптора (длина полезных данных) DSCTxQ.Write(msg, len), DSCUARTTxIrqEn(); // запись полезных данных, активация отправки else reterr = 1; return reterr; } Либо городить еще и реализацию Write(), аргументами которой будет несколько пар "msg-len", чего не хочется (я файлик с реализацией FIFO вообще трогать не хочу). На самом деле все несколько не так... Если очередь была пуста (механизм вычитки из нее и отправки по UART еще не запущен), то вызов SendMsg() добавит текущее сообщение в очередь на отправку и активирует прерывание. Прерывание будет постепенно вычитывать из очереди, формировать все эти обрамления фрейма, перекодировку экскейпов и т.д., но ведь в это время я могу напихивать в очередь еще порций дескрипторов + данных. В этом случае, когда прерывание закончит работу над первым сообщением, увидит в очереди следующий дескриптор + следующее сообщение и начнет обрабатывать его. Т.е. цепочечная обработка вырисовывается. В прерывании предусмотрен случай, когда данные нужно отправить, а в очереди их пока еще нет - т.к. поток не успел до конца дописать передаваемое сообщение, например. Просто выключается прерывание по передаче, а как только в очередь допишутся все остальные данные, SendMsg() активирует это прерывание снова, в котором механизм вычитыки продолжится с места, где остановился прошлый раз. Тут все несколько интереснее: на передачу занулять не нужно, т.к. я в прикладном коде всегда знаю, сколько мне надо отправить, поэтому дескриптор заполняется вначале и однозначно (см. SendMsg()). А вот прием... Мы же априори не знаем, какой длины сообщение мы ждем: я пихаю байты в очередь по мере поступления их в UART, т.е. где-то в приемном ISR по UART у меня вызвается метод приемной очереди Read(rxbyte, 1) (1 байт). Со стороны приложения (если очень часто мониторить занятость очереди) можно увидеть постепенное ее заполнение. Так вот в данном случае зануление дескриптора даст потоку (который выгребает уже готовенькие сообщения для прикладного ПО) понять, что сообщение в очереди пока что не полностью пришло. Ведь иначе точно также понадобится промежуточный буфер под хранение сообщения (чтобы разом потом записать и дескриптор, и данные). Поэтому у меня процесс вычитыки и обработки сообщений построен периодическим мониторингом кол-ва занятого места в приемной очереди. Как только я вижу, что в ней заполнилось места как минимум на дескриптор (4 байта у меня), я его (этот дескриптор) неинтрузивно вычитываю (там будет 0, пока сообщение не придет полностью (то самое зануление работает именно для этого)). Как только в этом дескрипторе появится что-то, отличное от 0 (ну и меньше максимально допустимого неким define-ом), это и есть len принятого сообщения. Теперь можно полноценно (с перемещением указателей) выкинуть из очереди дескриптор и вычитать данные - это и будет сообщение для прикладного ПО. Вот пример, как я это делаю
  12. Понял, подход в целом немного другой, просто я так не делаю: представьте обратную задачу - по UART принимается SLIP-поток (почти непрерывно) различных фреймов. Как их собирать в сообщения, когда, а главное - кто тот процесс, который этим занимается? У меня, например, тут же в прерывании по приему байта по UART идет "распаковка" протокола. При обнаружении начала очередного кадра резервирую место в очереди под дескриптор, и зануляю его. Вытаскиваю из потока байты, при необходимости конвертирую согласно фреймингу, пихаю в очередь. Как только обнаружил конец фрейма - заполняю ранее подготовленное место под дескриптор. Как итог - имею очередь из последовательностей "дескриптор-сообщение (соответсвующее этому дескриптору)". С точки зрения архитектуры ПО, ИМХО, проще и логичнее обрабатывать.
  13. Приветствую! Из названия, как обычно, нифига не понятно; сейчас объясню. При разработке ПО под МК или другие процессорные девайсы я часто пользуюсь очередьми. Обычные программные FIFO без наворотов и изысков. Все, что медленное (оносительно скорости процессора) - летит через очередь. Например, принты по отладочному UART летят через очередь, всякие транзакции между SPI-часами тоже через нее, чаще всего, прокладываю (конечно же, очереди разные). Короче, там где можно - очередь. Получается некая эмуляция доступа к букету низкоскоростных приблуд без бесполезных фризов процессора на ожидание флагов периферии и т.д. Всякие протоколы обменов у меня, в общем-то, тоже через очереди - по сути, из приемной выгребаю входные сообщения, в передающую кладу то, что надо передать. Вот я и подвел к сути проблемы (да и не проблема это, а всего лишь желание узнать, кто как делает). Допустим, есть протокол SLIP, который пакует пользовательские данные в фреймы и гоняет по UART-проводам. API драйвера предоставляет некую функцию SendMsg(void *data, u32 len), и когда мы хотим отправить N байт сообщения, мы вызываем SendMsg(msg, N). SLIP не накладывает никаких ограничений на длину фреймов, поэтому самый гибкий вариант - уметь передавать фреймы любой длины. Внутри SendMsg() должна подготовить некий дескриптор-заголовок, опираясь на длину сообщения N, записать его в очередь, а затем записать N байт данных в очередь, и при необходимости дать отмашку для запуска UART. Процесс, который непосредственно выгребает данные из очереди (пусть это будет прерывание по завершению передачи байта по UART), должен уметь отделять мух от котлет дескрипторы от данных, чтобы формировать символы-ограничители кадров (обычно я формирую SLIP-фрейм налету, без промежуточного буфера). Т.е. в SendMsg() проверяется, вместится ли в очередь передачи дескриптор (длина сообщения) + количество байт, указанное в дескрипторе: если уместится, то в нее пишется этот дескриптор, а затем - данные. Процесс, выгребающий эти данные из очереди, должен неинтрузивно определить, есть ли данные в очереди: если есть - вычитать дескриптор, по нему определить, сколько данных надо отправить, и если размер занятого количества данных в этой очереди больше или равен этому "сколько", начать отправку (иначе - это ситуация, когда SendMsg() только заполнил дескриптор, но не успел дописать все данные, в этом нет ничего страшного: прерывание UART можно отключить, а SendMsg() все равно запустит процесс отправки по завершении записи в очередь). Логика линейна, от гонок избавлена, все безопасно, но это лишь мне так кажется. Другой вариант - дескрипторы в отдельную очередь класть, данные - в отдельную. Это, КМК, безопаснее с точки зрения целостности очереди. Имея дескриптор определенного размера, мы никогда не получим шанса сдвига его положения в очереди дескрипторов. Т.е. вопрос больше даже религиозный - лить в одну очередь или в разные - кто как делает?
  14. На референсе могли быть действительные переходы на другую сторону или на внутренние слои, тогда стабов бы не было или они были бы не большими. Но при разводке, допустим, стало понятно, что RX-лэйны (а это именно они) можно протащить по top-у и они их протащили. Т.е. прероутинг был выполнен, а в финальном этапе про эти пары забыли. Хотя, ИМХО, правильно настроенные правила должны были ругаться на стабы. ИМХО, если пробник позиционируется как логический, то внутри обычный редрайвер/ретаймер, только в одну сторону. Нашел тут вот такое про пробинг PCIe. Интересно, однако Значит 100% ретаймеры.