#комбинаторика #H-мост #sensitivity #алгоритмы #математическая логика #FSM #русская смекала
Постановка задачи
Вам с out source производства принесли 600 электронных плат (PCB). Или Вам принесли 5 экземпляров самой первой ревизии PCB прототипа нового сложного электронного продукта прямо с поверхностного монтажа.
Как проверить, что в электронных платах отсутствует брак монтажа: короткие замыкания на GND или VCC, короткие замыкания соседних пинов друг на друга и прочие аппаратные ошибки?
определимся с терминологией
1-- пин (pin) - это металлический проводник, который торчит из микросхемы. В нашем случае микроконтроллера. В современных микроконтроллерах десяти и даже сотни пинов.
2-- граф - это множество вершин и ребер (палочки и кружочки).
3-- ориентированный граф — граф у которого ребра имеют направление
4--конечный автомат - ориентированный граф. Вершины - это состояния, ребра - события
5--конечный автомат Мили- это конечный автомат у которого действия происходят в момент переходов
6--конечный автомат Мура - это конечный автомат у которого действия происходят в состоянии
7--подтяжка к GND- соединение пина и GND резистором 40kOm
8--подтяжка к VCC- соединение пина и VCC резистором 40kOm
9-- токен - текстовая строчка без пробелов. Служит ссылкой на более длинное предложение
10- bring-up PCB- процесс отладки электронной платы с производства. Выявление неисправностей, несоответствий и их ремонт.
По-хорошему надо тестировать PCB на короткие замыкания и разрыв проводников еще до того как на печатную плату будут припаиваться компоненты. А для этого нужны тестировочные стенды (test jig) под каждую версию PCB. Разработку test jig могут позволить себе далеко не все организации. Как можно в более кустарных условиях протестировать электронную плату на предмет качества пайки?
Подойдем к решению постановки задачи издалека. Существует огромный класс электронных устройств, основой которых является микроконтроллер. Как вы думаете зачем в микроконтроллерах есть такая аппаратная функция как подтяжки напряжения к GND, VCC или вовсе отключенные подтяжки? Иногда это нужно для работы интерфейсов например I2C, 1-Wire, UART Rx, кнопок, но есть и более подходящая работа для подтяжек пинов.
Самое главное достоинство подтяжек напряжения в том, что установкой подтяжки невозможно, что бы то ни было сжечь на электронное плате. Подтяжка всегда подключает резистор очень высокого сопротивления 10kOm-40kOm, поэтому и токи в электронной цепи протекают незначительные (80 uA ... 300 uA).
Суть этого текста в том, что если правильным образом манипулировать подтяжками напряжений на пинах, то можно распознать такие высокоуровневые события как замыкание проводов друг на друга, или замыкания конкретного провода на GND или VCC.
У меня уже был текст про конечный автомат Load-detect для первоначального грубого контроля качества пайки. Вот он https://habr.com/ru/articles/756572/. Однако в том алгоритме было одно существенное ограничение. Тот конечный автомат принципиально не мог определить короткое замыкание между соседними пинами микроконтроллера. А это достаточно частый вид брака в массовом производстве Hi-Tech электроники. Если говорить откровенно, то тот первый Load-detect с FSM на 3 состояния - это уровень простейших многоклеточных. Сегодня же поговорим о настоящем динозавре под названием Cross-Detect.
Вообще я долго не мог понять как назвать весь этот механизм определения брака, чтобы создать для него отдельную папку в репозитории. Были версии назвать это solder-test, assembly-check, wire-verify. Однако название cross-detect лучше всего отражает суть этого алгоритма.
В этом тексте я представил Cross-Detect алгоритм, который помимо замыканий на землю и питание может распознать ещё и короткое замыкание не только между соседними пинами, но и между любыми проводниками на электронной плате (PCB)! Это просто мечта любого человека, который занимается BringUp(ом) электронных плат с производства.
Основная идея алгоритма в том, чтобы взять два различных микроконтроллерных пина и рассматривать их как H-мост. Вот так выглядит электрическая цепь H-моста.
Про то как делать распознавание оторванной нагрузки в силовом H-мосте у меня был отдельный культовый текст https://habr.com/ru/articles/709374/. Здесь же в контроле качества пайки PCB ситуация наоборот. Оторванная нагрузка это как раз благоприятный вариант. Тут будет всё как в том тексте про регистрацию обрыва нагрузки с той лишь разницей, что вместо плечей моста будут выступать просто два пина микроконтроллера и появилась еще и подтяжка к земле.
Я описал полный путь от идеи до реализации.
Какие могут быть проблемы в H-мосте?
Да целая куча. Могут быть так, что левое плечо замкнуто на землю, а правое на VCC. Может быть так, что пины вдруг замкнулись друг на друга там, где этого не должно происходить. Или оба пина замкнуты на VCC.
Как же программно выявить брак пайки?
Как водится в программировании, разработка любого программного компонента сводится к тому, что надо реализовать специфический конечный автомат (FSM) для этой задачи. Конечный автомат это золотой шаблон в программировании. Конечные автоматы хороши тем, что процесс разработки FSM лет как сто поставлен на рельcы и состоит из шести чисто формальных фаз.
Фаза 1. Определить входы конечного автомата.
С входами тут всё очень просто. Вход только один. Это событие переполнения таймера окончания переходного процесса. Таймер может быть как аппаратный, так и программный.
Из прошлой статьи (Load-Detect для Проверки Качества Пайки) можно заметить, что при установке подтяжки напряжения реальная длительность переходного процесса редко превышает 3 ms. Но я для надежности поставил 10...100ms.
Фаза 2. Перечислить все возможные состояния конечного автомата.
Тут чистая комбинаторика. Сколько способов установить три различные подтяжки на 2 пина? Согласно правилу произведения, 3*3=9 способов. Значит у нас будет девять состояний.
Фаза 3. Определить действия для данного конечного автомата
У каждого конечного автомата есть действия, которые он может делать (легальные действия) и действия, которые он не может делать. Конечному автомату контроля пайки достаточно всего-навсего вот этих девяти действий.
Фаза 4. Построить таблицу переходов
Есть одна особенность. Переходить из одного состояния в другое надо так, чтобы каждый раз устанавливалась только одна подтяжка. Это нужно по двум причинам:
1)для уменьшения энергопотребления микроконтроллера,
2)для уменьшения времени переходного процесса. Один переходной процесс всегда закончится быстрее чем два параллельных переходных процесса.
В двоичной системе счисления такие коды, где меняется только один бит - называются кодами Грея. У нас же тут, по сути, тоже коды Грея, только в третичной системе счисления.
Фаза 5. Построить таблицу выходов (Action Table)
В общем, на каждом переходе конечный автомат делает одно и тоже: перезапускает таймер. Однако на последнем переходе автомат увеличивает счетчик итераций, вычисляет решение, сохраняет решение в отчет и, если надо, выбирает следующую пару пинов для анализа коротких замыканий.
Тут можно заметить, что благодаря конечно автоматной методологии написание кода по сути сводится к составлению табличек. А таблички, в свою очередь, идеально представляются массивами в языках программирования. В самой прошивке в коде на Си таблицу переходов и таблицу выходов можно для компактности объединить в одной константной статической таблице. Вот и получается, что лучший алгоритм - это таблица.
Фаза 6. Нарисовать граф конечного автомата
Граф можно нарисовать вручную в программе inkscape (как я и сделал) или сгенерировать на языке генерации авто-документации Graphviz. Тут уж кому как удобнее. Вот такой получился простенький граф (рис.5.)для конечного автомата проверки качества пайки. Тут всё как на ладони: и таблица переходов, и таблица выходов отражены в одной картинке. Как говорит английская народная пословица: "картинка стоит тысячи слов" (А picture is worth a thousand words).
Для чистоты эксперимента в каждый отдельный момент времени работает обработка только одной пары пинов. Конечный автомат cross-detect прокрутится N итераций, вычислит решение для пары пинов и занесет его в матрицу решений.
Конечный автомат Cross-Detect это конечный автомат Мура, так как тут состояние, как раз, напрямую соответствует конфигурации подтяжек напряжения. Однако он же автомат Мили, так как все реальные действия по переключению подтяжек происходят в момент перехода между состояниями.
Фаза 7. Сформировать Look-Up таблицу для принятия решения
Вот тут-то и начинается вся аналитика и комбинаторика. Формально, если у нас есть два GPIO пина, которые выходят через форточку на улицу, то согласно комбинаторике, GPIO микроконтроллера может измерить только четыре различные состояния этой пары.
№ |
Left GPIO pin |
Right GPIO пин |
1 |
0.0 V |
0 |
2 |
0.0 V |
3.3 V |
3 |
3.3 V |
0.0 V |
4 |
3.3 V |
3.3 V |
При этом конечный автомат сross-detect за одну итерацию пробегает через девять состояний. И в каждом состоянии формально может быть четыре различных исходов измерения GPIO. Таким образом таблица принятия решения получится из 9*4 = 36 строчек.
Задача усложняется тем, что в H-мосте может быть одновременно две неисправности. Например на левом плече короткое замыкание на землю, на правом плече короткое замыкание на питание. Один H-мост и две неисправности.
Поэтому переменная, которая отвечает за обнаруженный код ошибки фактически должна быть битовым полем на 5 бит.
Порядковый номер бита |
Назначение |
1 |
0 |
0 |
На левом пине КЗ на GND |
есть неисправность |
нет неисправности |
1 |
На левом пине КЗ на VCC |
есть неисправность |
нет неисправности |
2 |
На правом пине КЗ на GND |
есть неисправность |
нет неисправности |
3 |
На правом пине КЗ на VCC |
есть неисправность |
нет неисправности |
4 |
пины замкнуты друг на друга |
есть неисправность |
нет неисправности |
Что является признаком того, что в H-мосте два пина не соединены перемычкой? Если GPIO на каждом пине показывают разные значение, значит перемычка отсутствует. Очевидно, что если в пайке оказалась клякса из припоя, то на пинах будет одинаковое напряжение.
Как работать с этой таблицей? Очень просто. После одной итерации конечного автомата в программе запускается функция bool IsLeftShortGnd(Node_t Node). Она проверяет, что в состояниях 2, 7 и 8 на левом плече моста всегда GPIO измеряет 0V.
Это и является необходимым и достатоным условием, что левый пин накоротко замкнут на GND.
Аналогично с выявлением короткого замыкания соседних пинов. Если в состояниях 2, 6 на обоих плечах GPIO измеряет 3.3V, а в состояниях 3, 9 GPIO измеряет 0 V, то это верный признак того, что эли два пина соединены перемычкой.
Для полноты картины покажем, что этот автомат также может выявлять короткие замыкания на VCC.
Когда на правом плече включена подтяжка к земле, а GPIO измеряет 3.3 V, то это явный признак, что на правом плече на самом-то деле короткое замыкание на VCC.
Как видите, нельзя делать выводы глядя только на одно состояние. Надо проанализировать измерения GPIO во всех девяти состояниях, чтобы однозначно сказать о том, какая именно неисправность присутствует в электрической цепи. По сути это яркий пример того как математическая логика пригождается в программировании микроконтроллеров для решения реальных задач из прода.
Фаза 8. Написать программный код на языке программирования Си
Вот код основного драйвера программного компонента cross-detect
Вот код основного драйвера программного компонента cross-detect
#include "cross_detect_drv.h"
#include <stdint.h>
#include <string.h>
#include "cross_detect_diag.h"
#include "data_utils.h"
#include "gpio_drv.h"
#include "gpio_general_diag.h"
#include "log.h"
#include "time_utils.h"
uint64_t cross_detect_period_us = MSEC_2_USEC(20);
static bool CrossDetectIsLeftHi(CrossDetectGpioRead_t read){
bool res = false;
switch((uint8_t)read){
case READ_L1_R0: res = true;break;
case READ_L1_R1: res = true; break;
case READ_L0_R0: res = false; break;
case READ_L0_R1: res = false; break;
}
return res;
}
static bool CrossDetectIsRightHi(CrossDetectGpioRead_t read){
bool res = false;
switch((uint8_t)read){
case READ_L0_R1: res = true; break;
case READ_L1_R1: res = true; break;
case READ_L0_R0: res = false; break;
case READ_L1_R0: res = false; break;
}
return res;
}
static bool CrossDetectIsRightLow(CrossDetectGpioRead_t read){
bool res = false;
switch((uint8_t)read) {
case READ_L0_R0: res = true; break;
case READ_L1_R0: res = true;break;
case READ_L0_R1: res = false; break;
case READ_L1_R1: res = false; break;
}
return res;
}
static bool CrossDetectIsLeftLow(CrossDetectGpioRead_t read){
bool res = false;
switch((uint8_t)read){
case READ_L0_R0: res = true; break;
case READ_L0_R1: res = true; break;
case READ_L1_R0: res = false; break;
case READ_L1_R1: res = false; break;
}
return res;
}
static bool cross_detect_is_left_gnd(CrossDetectPairInfo_t* pair ){
bool res = false;
res = CrossDetectIsLeftLow(pair->measurements[CROSS_DETECT_STATE_LU_RA].read);
if(res){
res=CrossDetectIsLeftLow(pair->measurements[CROSS_DETECT_STATE_LU_RU].read);
if(res){
res=CrossDetectIsLeftLow(pair->measurements[CROSS_DETECT_STATE_LU_RD].read);
}
}
return res;
}
static bool cross_detect_is_left_vcc(CrossDetectPairInfo_t* pair ){
bool res = false;
res = CrossDetectIsLeftHi(pair->measurements[CROSS_DETECT_STATE_LD_RA].read);
if(res) {
res = CrossDetectIsLeftHi(pair->measurements[CROSS_DETECT_STATE_LD_RD].read);
if(res) {
res=CrossDetectIsLeftHi(pair->measurements[CROSS_DETECT_STATE_LD_RU].read);
}
}
return res;
}
static bool cross_detect_is_right_gnd(CrossDetectPairInfo_t* pair ){
bool res = false;
res = CrossDetectIsRightLow(pair->measurements[ CROSS_DETECT_STATE_LD_RU].read);
if(res){
res=CrossDetectIsRightLow(pair->measurements[CROSS_DETECT_STATE_LA_RU].read);
if(res){
res=CrossDetectIsRightLow(pair->measurements[CROSS_DETECT_STATE_LU_RU].read);
}
}
return res;
}
static bool cross_detect_is_right_vcc(CrossDetectPairInfo_t* pair ){
bool res = false;
res = CrossDetectIsRightHi(pair->measurements[CROSS_DETECT_STATE_LU_RD].read);
if(res) {
res = CrossDetectIsRightHi(pair->measurements[CROSS_DETECT_STATE_LD_RD].read);
if(res) {
res=CrossDetectIsRightHi(pair->measurements[CROSS_DETECT_STATE_LA_RD].read);
}
}
return res;
}
static bool cross_detect_is_cross(CrossDetectPairInfo_t* pair ){
bool res = false;
if(READ_L1_R1==pair->measurements[CROSS_DETECT_STATE_LU_RA].read){
if(CROSS_DETECT_STATE_LU_RA==pair->measurements[CROSS_DETECT_STATE_LU_RA].state){
if(READ_L1_R1==pair->measurements[CROSS_DETECT_STATE_LA_RU].read){
if(CROSS_DETECT_STATE_LA_RU==pair->measurements[CROSS_DETECT_STATE_LA_RU].state){
res = true;
}
}
}
}
if(res){
res = false;
if(READ_L0_R0==pair->measurements[CROSS_DETECT_STATE_LA_RD].read){
if(READ_L0_R0==pair->measurements[CROSS_DETECT_STATE_LD_RA].read){
res =true;
}
}
}
return res;
}
static bool cross_detect_run_analitic(CrossDetectHandle_t* const Node) {
bool res = false;
LOG_PARN(CROSS_DETECT, "RunAnalytics");
if(Node) {
CrossDetectState_t state = 0;
Node->pair.fault_cnt = 0;
Node->pair.Fault.fault_code = 0;
res = cross_detect_is_cross(&Node->pair);
if(res){
Node->pair.Fault.cross = 1;
}else{
Node->pair.Fault.cross = 0;
}
res = cross_detect_is_left_gnd(&Node->pair);
if(res){
Node->pair.Fault.left_short_gnd = 1;
}else{
Node->pair.Fault.left_short_gnd = 0;
}
res = cross_detect_is_right_gnd(&Node->pair);
if(res){
Node->pair.Fault.right_short_gnd = 1;
}else{
Node->pair.Fault.right_short_gnd = 0;
}
res = cross_detect_is_left_vcc(&Node->pair);
if(res){
Node->pair.Fault.left_short_vcc = 1;
}else{
Node->pair.Fault.left_short_vcc = 0;
}
res = cross_detect_is_right_vcc(&Node->pair);
if(res){
Node->pair.Fault.right_short_vcc = 1;
}else{
Node->pair.Fault.right_short_vcc = 0;
}
if(Node->pair.Fault.fault_code != CrossDetectResult[Node->left_num][Node->right_num].FaultPrev.fault_code) {
if(Node->pair.Fault.fault_code) {
res = CrossDetectDiagFault(Node);
}
}
CrossDetectResult[Node->left_num][Node->right_num].Fault.fault_code = Node->pair.Fault.fault_code;
CrossDetectResult[Node->left_num][Node->right_num].FaultPrev.fault_code = Node->pair.Fault.fault_code;
CrossDetectResult[Node->left_num][Node->right_num].update_cnt++;
res = true;
}
return res;
}
/* a b m a b m
* (0, 4, 5) ->(1, 0, 5)*/
U16Pair_t calc_next_uniq_u16pair(U16Pair_t pair) {
U16Pair_t out;
out = pair;
if(out.a < out.max ) {
if(out.b < out.max ) {
out.b++;
if(out.max ==out.b){
out.a++;
out.b = 0;
}
} else {
out.a++;
out.b = 0;
}
} else {
out.a = 0;
out.b = 1;
}
if(out.max==out.a){
out.a=0;
}
if(out.a == out.b) {
if(out.b < (out.max - 1)) {
out.b++;
} else {
out.b = 0;
if(out.a < (out.max - 1)) {
out.a++;
} else {
out.a = 0;
}
}
}
return out;
}
static bool cross_detect_set_next_pair(CrossDetectHandle_t* const Node) {
bool res = false;
if(Node) {
Node->pair.spin_cnt = 0;
uint32_t cnt = cross_detect_get_pin_cnt();
res = cross_detect_reset_pair(&Node->pair);
U16Pair_t in_pair = {
.a = Node->left_num,
.b = Node->right_num,
.max = cnt,
};
U16Pair_t out = calc_next_uniq_u16pair(in_pair);
Node->left_num = out.a;
Node->right_num = out.b;
LOG_DEBUG(CROSS_DETECT,"%u,%u",Node->left_num ,Node->right_num );
if( (cnt*cnt-cnt)<Node->pair_cnt ){
if(0==( Node->pair_cnt%(cnt*cnt-cnt) ) ){
uint32_t diratin_ms = time_get_ms32() - Node->start_ms;
LOG_WARNING(CROSS_DETECT,"All %u PairsSolved!, %u ms",cnt*cnt-cnt,diratin_ms);
Node->start_ms = time_get_ms32();
}
}
Node->pair_cnt++;
if(Node->left_num != Node->right_num) {
res = cross_detect_init_pair(&Node->pair, CrossDetectPinConfig[Node->left_num].pad,
CrossDetectPinConfig[Node->right_num].pad);
}
}
return res;
}
static bool cross_detect_cycle_complete(CrossDetectHandle_t* const Node) {
bool res = false;
LOG_PARN(CROSS_DETECT, "CycleComplete");
if(Node) {
Node->pair.spin_cnt++;
res = cross_detect_run_analitic(Node);
if(CROSS_DETECT_TRY_PER_PAIR < Node->pair.spin_cnt) {
res = cross_detect_set_next_pair(Node);
}
}
return res;
}
static bool cross_detect_next_state(CrossDetectHandle_t* const Node) {
bool res = true;
LOG_PARN(CROSS_DETECT, "NextState");
return res;
}
static bool cross_detect_start(CrossDetectHandle_t* const Node) {
bool res = true;
LOG_PARN(CROSS_DETECT, "Start");
return res;
}
static const CrossDetectStateInfo_t StateTableLookUpTable[] = {
[CROSS_DETECT_STATE_LA_RA] =
{
.pull_left = GPIO__PULL_AIR,
.pull_right = GPIO__PULL_AIR,
.action = cross_detect_start,
.state_new = CROSS_DETECT_STATE_LU_RA,
}, /*Left Pull air,Right pull air*/
[CROSS_DETECT_STATE_LU_RA] =
{
.pull_left = GPIO__PULL_UP,
.pull_right = GPIO__PULL_AIR,
.action = cross_detect_next_state,
.state_new = CROSS_DETECT_STATE_LD_RA,
}, /*Left pull up,Right pull air*/
[CROSS_DETECT_STATE_LD_RA] =
{
.pull_left = GPIO__PULL_DOWN,
.pull_right = GPIO__PULL_AIR,
.action = cross_detect_next_state,
.state_new = CROSS_DETECT_STATE_LD_RD,
}, /*Left pull down,Right pull air*/
[CROSS_DETECT_STATE_LD_RD] =
{
.pull_left = GPIO__PULL_DOWN,
.pull_right = GPIO__PULL_DOWN,
.action = cross_detect_next_state,
.state_new = CROSS_DETECT_STATE_LD_RU,
}, /*Left pull down,Right pull down*/
[CROSS_DETECT_STATE_LD_RU] =
{
.pull_left = GPIO__PULL_DOWN,
.pull_right = GPIO__PULL_UP,
.action = cross_detect_next_state,
.state_new = CROSS_DETECT_STATE_LA_RU,
}, /*Left pull down,Right pull up*/
[CROSS_DETECT_STATE_LA_RU] =
{
.pull_left = GPIO__PULL_AIR,
.pull_right = GPIO__PULL_UP,
.action = cross_detect_next_state,
.state_new = CROSS_DETECT_STATE_LU_RU,
}, /*Left pull air,Right pull up*/
[CROSS_DETECT_STATE_LU_RU] =
{
.pull_left = GPIO__PULL_UP,
.pull_right = GPIO__PULL_UP,
.action = cross_detect_next_state,
.state_new = CROSS_DETECT_STATE_LU_RD,
}, /*Left pull up,Right pull up*/
[CROSS_DETECT_STATE_LU_RD] =
{
.pull_left = GPIO__PULL_UP,
.pull_right = GPIO__PULL_DOWN,
.action = cross_detect_next_state,
.state_new = CROSS_DETECT_STATE_LA_RD,
}, /*Left pull up,Right pull down*/
[CROSS_DETECT_STATE_LA_RD] =
{
.pull_left = GPIO__PULL_AIR,
.pull_right = GPIO__PULL_DOWN,
.action = cross_detect_cycle_complete,
.state_new = CROSS_DETECT_STATE_LA_RA,
}, /*Left pull air,Right pull down*/
};
static bool cross_detect_state_set(CrossDetectPairInfo_t* const pair, CrossDetectState_t new_state) {
bool res = false;
if(pair) {
uint8_t ok = 0;
res = gpio_set_pull(pair->left.byte, StateTableLookUpTable[new_state].pull_left);
if(res) {
ok++;
}
res = gpio_set_pull(pair->right.byte, StateTableLookUpTable[new_state].pull_right);
if(res) {
ok++;
}
if(2 == ok) {
pair->state = new_state;
res = true;
} else {
LOG_ERROR(CROSS_DETECT, "SetStateErr %u=%s", new_state, CrossDetectState2Str(new_state));
res = false;
}
}
return res;
}
const CrossDetectSolutionInfo_t CrossDetectSolutionInfo[36] = {
{
/*1*/ .state = CROSS_DETECT_STATE_LA_RA,
.read = READ_L0_R0,
.left_short_gnd = CONF_NO,
.right_short_gnd = CONF_NO,
.left_short_vcc = CONF_NO,
.right_short_vcc = CONF_NO,
.cross = CONF_NO,
.open = CONF_NO,
.error = CONF_NO,
.ok = CONF_YES,
},
{
/*2*/ .state = CROSS_DETECT_STATE_LU_RA,
.read = READ_L0_R0,
.left_short_gnd = CONF_YES,
.left_short_vcc = CONF_NO,
.right_short_gnd = CONF_NO,
.right_short_vcc = CONF_NO,
.cross = CONF_MAYBE,
.open = CONF_MAYBE,
.error = CONF_YES,
.ok = CONF_NO,
},
{
/*3*/ .state = CROSS_DETECT_STATE_LD_RA,
.read = READ_L0_R0,
.left_short_gnd = CONF_MAYBE,
.left_short_vcc = CONF_NO,
.right_short_gnd = CONF_MAYBE,
.right_short_vcc = CONF_NO,
.cross = CONF_MAYBE,
.open = CONF_MAYBE,
.error = CONF_NO,
.ok = CONF_YES,
},
{
/*4*/ .state = CROSS_DETECT_STATE_LD_RD,
.read = READ_L0_R0,
.left_short_gnd = CONF_MAYBE,
.left_short_vcc = CONF_NO,
.right_short_gnd = CONF_MAYBE,
.right_short_vcc = CONF_NO,
.cross = CONF_MAYBE,
.open = CONF_MAYBE,
.error = CONF_NO,
.ok = CONF_YES,
},
{
/*5*/ .state = CROSS_DETECT_STATE_LD_RU,
.read = READ_L0_R0,
.left_short_gnd = CONF_MAYBE,
.left_short_vcc = CONF_NO,
.right_short_gnd = CONF_YES,
.right_short_vcc = CONF_NO,
.cross = CONF_MAYBE,
.open = CONF_MAYBE,
.error = CONF_YES,
.ok = CONF_NO,
},
{
/*6*/ .state = CROSS_DETECT_STATE_LA_RU,
.read = READ_L0_R0,
.left_short_gnd = CONF_MAYBE,
.left_short_vcc = CONF_NO,
.right_short_gnd = CONF_YES,
.right_short_vcc = CONF_NO,
.cross = CONF_MAYBE,
.open = CONF_MAYBE,
.error = CONF_YES,
.ok = CONF_NO,
},
{/*7*/ .state = CROSS_DETECT_STATE_LU_RU,
.read = READ_L0_R0,
.left_short_gnd = CONF_YES,
.left_short_vcc = CONF_NO,
.right_short_gnd = CONF_YES,
.right_short_vcc = CONF_NO,
.cross = CONF_MAYBE,
.open = CONF_MAYBE,
.error = CONF_YES,
.ok = CONF_NO},
{/*8*/ .state = CROSS_DETECT_STATE_LU_RD,
.read = READ_L0_R0,
.left_short_gnd = CONF_YES,
.left_short_vcc = CONF_NO,
.right_short_gnd = CONF_MAYBE,
.right_short_vcc = CONF_NO,
.cross = CONF_MAYBE,
.open = CONF_MAYBE,
.error = CONF_YES,
.ok = CONF_NO},
{/*9*/ .state = CROSS_DETECT_STATE_LA_RD,
.read = READ_L0_R0,
.left_short_gnd = CONF_MAYBE,
.left_short_vcc = CONF_NO,
.right_short_gnd = CONF_MAYBE,
.right_short_vcc = CONF_NO,
.cross = CONF_MAYBE,
.open = CONF_MAYBE,
.error = CONF_NO,
.ok = CONF_YES},
{/*10*/ .state = CROSS_DETECT_STATE_LA_RA,
.read = READ_L0_R1,
.left_short_gnd = CONF_MAYBE,
.left_short_vcc = CONF_NO,
.right_short_gnd = CONF_NO,
.right_short_vcc = CONF_MAYBE,
.cross = CONF_NO,
.open = CONF_YES,
.error = CONF_YES,
.ok = CONF_NO},
{/*11*/ .state = CROSS_DETECT_STATE_LU_RA,
.read = READ_L0_R1,
.left_short_gnd = CONF_YES,
.left_short_vcc = CONF_NO,
.right_short_gnd = CONF_NO,
.right_short_vcc = CONF_MAYBE,
.cross = CONF_NO,
.open = CONF_YES,
.error = CONF_YES,
.ok = CONF_NO},
{/*12*/ .state = CROSS_DETECT_STATE_LD_RA,
.read = READ_L0_R1,
.left_short_gnd = CONF_MAYBE,
.left_short_vcc = CONF_NO,
.right_short_gnd = CONF_NO,
.right_short_vcc = CONF_MAYBE,
.cross = CONF_NO,
.open = CONF_YES,
.error = CONF_YES,
.ok = CONF_NO},
{/*13*/ .state = CROSS_DETECT_STATE_LD_RD,
.read = READ_L0_R1,
.left_short_gnd = CONF_MAYBE,
.left_short_vcc = CONF_NO,
.right_short_gnd = CONF_NO,
.right_short_vcc = CONF_YES,
.cross = CONF_NO,
.open = CONF_YES,
.error = CONF_YES,
.ok = CONF_NO},
{/*14*/ .state = CROSS_DETECT_STATE_LD_RU,
.read = READ_L0_R1,
.left_short_gnd = CONF_MAYBE,
.left_short_vcc = CONF_NO,
.right_short_gnd = CONF_NO,
.right_short_vcc = CONF_MAYBE,
.cross = CONF_NO,
.open = CONF_YES,
.error = CONF_NO,
.ok = CONF_YES},
{/*15*/ .state = CROSS_DETECT_STATE_LA_RU,
.read = READ_L0_R1,
.left_short_gnd = CONF_MAYBE,
.left_short_vcc = CONF_NO,
.right_short_gnd = CONF_NO,
.right_short_vcc = CONF_MAYBE,
.cross = CONF_NO,
.open = CONF_YES,
.error = CONF_NO,
.ok = CONF_YES},
{/*16*/ .state = CROSS_DETECT_STATE_LU_RU,
.read = READ_L0_R1,
.left_short_gnd = CONF_YES,
.left_short_vcc = CONF_NO,
.right_short_gnd = CONF_NO,
.right_short_vcc = CONF_MAYBE,
.cross = CONF_NO,
.open = CONF_YES,
.error = CONF_YES,
.ok = CONF_NO},
{/*17*/ .state = CROSS_DETECT_STATE_LU_RD,
.read = READ_L0_R1,
.left_short_gnd = CONF_YES,
.left_short_vcc = CONF_NO,
.right_short_gnd = CONF_NO,
.right_short_vcc = CONF_YES,
.cross = CONF_NO,
.open = CONF_YES,
.error = CONF_YES,
.ok = CONF_NO},
{/*18*/ .state = CROSS_DETECT_STATE_LA_RD,
.read = READ_L0_R1,
.left_short_gnd = CONF_MAYBE,
.left_short_vcc = CONF_NO,
.right_short_gnd = CONF_NO,
.right_short_vcc = CONF_YES,
.cross = CONF_NO,
.open = CONF_YES,
.error = CONF_YES,
.ok = CONF_NO},
{/*19*/ .state = CROSS_DETECT_STATE_LA_RA,
.read = READ_L1_R0,
.left_short_gnd = CONF_NO,
.left_short_vcc = CONF_MAYBE,
.right_short_gnd = CONF_MAYBE,
.right_short_vcc = CONF_NO,
.cross = CONF_NO,
.open = CONF_YES,
.error = CONF_YES,
.ok = CONF_NO},
{/*20*/ .state = CROSS_DETECT_STATE_LU_RA,
.read = READ_L1_R0,
.left_short_gnd = CONF_NO,
.left_short_vcc = CONF_MAYBE,
.right_short_gnd = CONF_MAYBE,
.right_short_vcc = CONF_NO,
.cross = CONF_NO,
.open = CONF_YES,
.error = CONF_NO,
.ok = CONF_YES},
{/*21*/ .state = CROSS_DETECT_STATE_LD_RA,
.read = READ_L1_R0,
.left_short_gnd = CONF_NO,
.left_short_vcc = CONF_YES,
.right_short_gnd = CONF_MAYBE,
.right_short_vcc = CONF_NO,
.cross = CONF_NO,
.open = CONF_YES,
.error = CONF_YES,
.ok = CONF_NO},
{/*22*/ .state = CROSS_DETECT_STATE_LD_RD,
.read = READ_L1_R0,
.left_short_gnd = CONF_NO,
.left_short_vcc = CONF_YES,
.right_short_gnd = CONF_MAYBE,
.right_short_vcc = CONF_NO,
.cross = CONF_NO,
.open = CONF_YES,
.error = CONF_YES,
.ok = CONF_NO},
{/*23*/ .state = CROSS_DETECT_STATE_LD_RU,
.read = READ_L1_R0,
.left_short_gnd = CONF_NO,
.left_short_vcc = CONF_YES,
.right_short_gnd = CONF_YES,
.right_short_vcc = CONF_NO,
.cross = CONF_NO,
.open = CONF_YES,
.error = CONF_YES,
.ok = CONF_NO},
{/*24*/ .state = CROSS_DETECT_STATE_LA_RU,
.read = READ_L1_R0,
.left_short_gnd = CONF_NO,
.left_short_vcc = CONF_MAYBE,
.right_short_gnd = CONF_YES,
.right_short_vcc = CONF_NO,
.cross = CONF_NO,
.open = CONF_YES,
.error = CONF_YES,
.ok = CONF_NO},
{/*25*/ .state = CROSS_DETECT_STATE_LU_RU,
.read = READ_L1_R0,
.left_short_gnd = CONF_NO,
.left_short_vcc = CONF_MAYBE,
.right_short_gnd = CONF_YES,
.right_short_vcc = CONF_NO,
.cross = CONF_NO,
.open = CONF_YES,
.error = CONF_YES,
.ok = CONF_NO},
{/*26*/ .state = CROSS_DETECT_STATE_LU_RD,
.read = READ_L1_R0,
.left_short_gnd = CONF_NO,
.left_short_vcc = CONF_MAYBE,
.right_short_gnd = CONF_MAYBE,
.right_short_vcc = CONF_NO,
.cross = CONF_NO,
.open = CONF_YES,
.error = CONF_NO,
.ok = CONF_YES},
{/*27*/ .state = CROSS_DETECT_STATE_LA_RD,
.read = READ_L1_R0,
.left_short_gnd = CONF_NO,
.left_short_vcc = CONF_MAYBE,
.right_short_gnd = CONF_MAYBE,
.right_short_vcc = CONF_NO,
.cross = CONF_NO,
.open = CONF_YES,
.error = CONF_YES,
.ok = CONF_NO},
{/*28*/ .state = CROSS_DETECT_STATE_LA_RA,
.read = READ_L1_R1,
.left_short_gnd = CONF_NO,
.left_short_vcc = CONF_MAYBE,
.right_short_gnd = CONF_NO,
.right_short_vcc = CONF_MAYBE,
.cross = CONF_MAYBE,
.open = CONF_MAYBE,
.error = CONF_YES,
.ok = CONF_NO},
{/*29*/ .state = CROSS_DETECT_STATE_LU_RA,
.read = READ_L1_R1,
.left_short_gnd = CONF_NO,
.left_short_vcc = CONF_MAYBE,
.right_short_gnd = CONF_NO,
.right_short_vcc = CONF_MAYBE,
.cross = CONF_YES,
.open = CONF_MAYBE,
.error = CONF_YES,
.ok = CONF_NO},
{/*30*/ .state = CROSS_DETECT_STATE_LD_RA,
.read = READ_L1_R1,
.left_short_gnd = CONF_NO,
.left_short_vcc = CONF_YES,
.right_short_gnd = CONF_NO,
.right_short_vcc = CONF_MAYBE,
.cross = CONF_MAYBE,
.open = CONF_MAYBE,
.error = CONF_YES,
.ok = CONF_NO},
{/*31*/ .state = CROSS_DETECT_STATE_LD_RD,
.read = READ_L1_R1,
.left_short_gnd = CONF_NO,
.left_short_vcc = CONF_YES,
.right_short_gnd = CONF_NO,
.right_short_vcc = CONF_YES,
.cross = CONF_MAYBE,
.open = CONF_MAYBE,
.error = CONF_YES,
.ok = CONF_NO},
{/*32*/ .state = CROSS_DETECT_STATE_LD_RU,
.read = READ_L1_R1,
.left_short_gnd = CONF_NO,
.left_short_vcc = CONF_YES,
.right_short_gnd = CONF_NO,
.right_short_vcc = CONF_MAYBE,
.cross = CONF_MAYBE,
.open = CONF_MAYBE,
.error = CONF_YES,
.ok = CONF_NO},
{/*33*/ .state = CROSS_DETECT_STATE_LA_RU,
.read = READ_L1_R1,
.left_short_gnd = CONF_NO,
.left_short_vcc = CONF_MAYBE,
.right_short_gnd = CONF_NO,
.right_short_vcc = CONF_MAYBE,
.cross = CONF_YES,
.open = CONF_MAYBE,
.error = CONF_YES,
.ok = CONF_NO},
{/*34*/ .state = CROSS_DETECT_STATE_LU_RU,
.read = READ_L1_R1,
.left_short_gnd = CONF_NO,
.left_short_vcc = CONF_MAYBE,
.right_short_gnd = CONF_NO,
.right_short_vcc = CONF_MAYBE,
.cross = CONF_MAYBE,
.open = CONF_MAYBE,
.error = CONF_NO,
.ok = CONF_YES},
{/*35*/ .state = CROSS_DETECT_STATE_LU_RD,
.read = READ_L1_R1,
.left_short_gnd = CONF_NO,
.left_short_vcc = CONF_MAYBE,
.right_short_gnd = CONF_NO,
.right_short_vcc = CONF_YES,
.cross = CONF_MAYBE,
.open = CONF_MAYBE,
.error = CONF_YES,
.ok = CONF_NO},
{/*36*/ .state = CROSS_DETECT_STATE_LA_RD,
.read = READ_L1_R1,
.left_short_gnd = CONF_NO,
.left_short_vcc = CONF_MAYBE,
.right_short_gnd = CONF_MAYBE,
.right_short_vcc = CONF_YES,
.cross = CONF_MAYBE,
.open = CONF_MAYBE,
.error = CONF_YES,
.ok = CONF_NO},
};
uint32_t cross_detect_get_lut_size(void) {
uint32_t cnt = ARRAY_SIZE(CrossDetectSolutionInfo);
return cnt;
}
static const CrossDetectSolutionInfo_t* GetSolNode(const CrossDetectStateMeasure_t* const measure_node) {
const CrossDetectSolutionInfo_t* LutNode = NULL;
uint32_t cnt = cross_detect_get_lut_size( );
uint32_t i = 0;
for(i = 0; i < cnt; i++) {
if(CrossDetectSolutionInfo[i].state == measure_node->state) {
if(CrossDetectSolutionInfo[i].read == measure_node->read) {
LutNode = &CrossDetectSolutionInfo[i];
break;
}
}
}
return LutNode;
}
CrossDetectHandle_t* CrossDetectGetNode(uint8_t num) {
CrossDetectHandle_t* Node = NULL;
uint32_t i = 0;
uint32_t cnt = cross_detect_get_cnt();
for(i = 0; i < cnt; i++) {
if(num == CrossDetectInstance[i].num) {
if(CrossDetectInstance[i].valid) {
Node = &CrossDetectInstance[i];
break;
}
}
}
return Node;
}
const CrossDetectConfig_t* CrossDetectGetConfNode(uint8_t num) {
const CrossDetectConfig_t* Config = NULL;
uint32_t i = 0;
uint32_t cnt = cross_detect_get_cnt();
for(i = 0; i < cnt; i++) {
if(num == CrossDetectConfig[i].num) {
if(CrossDetectConfig[i].valid) {
Config = &CrossDetectConfig[i];
break;
}
}
}
return Config;
}
bool cross_detect_init_pair(CrossDetectPairInfo_t* const pair, Pad_t left, Pad_t right) {
bool res = false;
if(pair) {
if(left.byte != right.byte) {
pair->left = left;
pair->right = right;
pair->solution = CROSS_DETECT_SOL_UNDEF;
//pair->prev_solution = CROSS_DETECT_SOL_UNDEF;
pair->state = CROSS_DETECT_STATE_LA_RA;
pair->spin_cnt = 0;
pair->err_cnt = 0;
pair->time_start = 0;
pair->pause_ms = 0;
pair->init = true;
CrossDetectDiagPair(pair);
res = cross_detect_state_set(pair, CROSS_DETECT_STATE_LA_RA);
}
}
return res;
}
bool cross_detect_reset_pair(const CrossDetectPairInfo_t* const pair){
bool res = false;
res = gpio_set_pull(pair->left.byte, GPIO__PULL_AIR);
res = gpio_set_pull(pair->right.byte, GPIO__PULL_AIR);
return res;
}
bool cross_detect_enable(uint8_t num, bool on_off) {
bool res = false;
CrossDetectHandle_t* Node = CrossDetectGetNode(num);
if(Node) {
Node->on = on_off;
res = true;
}
return res;
}
static bool cross_detect_init_pin(const CrossDetectPinConfig_t* const PinConfig) {
bool res = false;
if(PinConfig) {
uint8_t ok = 0;
LOG_WARNING(CROSS_DETECT, "InitPad: %s In PullAir", GpioPad2Str(PinConfig->pad.byte));
res = gpio_set_dir(PinConfig->pad.byte, GPIO_DIR_IN);
if(res) {
ok++;
} else {
LOG_ERROR(CROSS_DETECT, "Pad: %s SetDirIn Err", GpioPad2Str(PinConfig->pad.byte));
}
res = gpio_set_pull(PinConfig->pad.byte, GPIO__PULL_AIR);
if(res) {
ok++;
} else {
LOG_ERROR(CROSS_DETECT, "Pad: %s SetPullAir Err", GpioPad2Str(PinConfig->pad.byte));
}
#ifdef HAS_NRF5340
res = gpio_set_pin_mcu(PinConfig->pad, NRF_GPIO_PIN_MUX_APP);
if(res) {
ok++;
} else {
LOG_ERROR(CROSS_DETECT, "Pad: %s SetAppCore Err", GpioPad2Str(PinConfig->pad.byte));
}
#endif
if(3 == ok) {
res = true;
} else {
res = false;
}
}
return res;
}
bool cross_detect_init_pins(uint8_t num) {
bool res = false;
uint32_t pin_cnt = cross_detect_get_pin_cnt();
LOG_WARNING(CROSS_DETECT, "LD%u Init %u Pins", num, pin_cnt);
uint32_t i;
uint32_t ok = 0;
for(i = 0; i < pin_cnt; i++) {
if(num == CrossDetectPinConfig[i].num) {
res = cross_detect_init_pin(&CrossDetectPinConfig[i]);
if(res) {
ok++;
LOG_DEBUG(CROSS_DETECT, "InitPin %s Ok", GpioPadToStr(CrossDetectPinConfig[i].pad));
} else {
LOG_ERROR(CROSS_DETECT, "InitPinErr %d", num);
}
}
}
if(0 < ok) {
res = true;
}
return res;
}
bool cross_detect_init_one(uint8_t num) {
bool res = false;
LOG_WARNING(CROSS_DETECT, "Init %d", num);
const CrossDetectConfig_t* Config = CrossDetectGetConfNode(num);
if(Config) {
CrossDetectHandle_t* Node = CrossDetectGetNode(num);
if(Node) {
Node->on = true;
Node->init = true;
Node->valid = true;
Node->pair_cnt = 0;
Node->left_num = 0;
Node->right_num = 1;
Node->start_ms = time_get_ms32();
//size_t size = sizeof(CrossDetectResult_t) * CROSS_DETECT_PIN_CNT * CROSS_DETECT_PIN_CNT;
//LOG_INFO(CROSS_DETECT, "ZeroSize %u byte of result array", size);
uint32_t l, r;
uint32_t cnt = cross_detect_get_pin_cnt();
for(l=0;l<cnt;l++){
for(r=0;r<cnt;r++){
CrossDetectResult[l][r].Fault.fault_code=0;
CrossDetectResult[l][r].FaultPrev.fault_code=0;
}
}
res = cross_detect_init_pins(num);
if(res) {
LOG_INFO(CROSS_DETECT, "%u InitPinsOk", num);
} else {
LOG_ERROR(CROSS_DETECT, "%u InitPinsErr", num);
}
res = cross_detect_init_pair(&Node->pair, CrossDetectPinConfig[Node->left_num].pad,
CrossDetectPinConfig[Node->right_num].pad);
if(res){
}else{
LOG_ERROR(CROSS_DETECT, "InitPair %u,%u Err",Node->left_num, Node->right_num);
}
} else {
LOG_ERROR(CROSS_DETECT, "%u NodeErr", num);
}
} else {
LOG_ERROR(CROSS_DETECT, "%u ConfErr", num);
}
return res;
}
bool cross_detect_init(void) {
bool res = false;
set_log_level(CROSS_DETECT, LOG_LEVEL_DEBUG);
uint32_t cnt = cross_detect_get_cnt();
uint32_t ok = 0;
LOG_WARNING(CROSS_DETECT, "Init Cnt %d", cnt);
uint32_t i = 0;
for(i = 1; i <= cnt; i++) {
res = cross_detect_init_one(i);
if(res) {
ok++;
LOG_INFO(CROSS_DETECT, "LD%u InitOk", i);
} else {
LOG_ERROR(CROSS_DETECT, "LD%u InitErr", i);
}
}
if(ok) {
res = true;
LOG_INFO(CROSS_DETECT, "Init %u Ok", ok);
} else {
res = false;
LOG_ERROR(CROSS_DETECT, "InitErr");
}
set_log_level(CROSS_DETECT, LOG_LEVEL_INFO);
return res;
}
static bool cross_detect_calc_fault(CrossDetectPairInfo_t* const pair, CrossDetectState_t state) {
bool res = false;
uint32_t i = 0;
/*Are any faults found in this state?*/
for(i = 0; i < 9; i++) {
if(pair->measurements[i].state == state) {
const CrossDetectSolutionInfo_t* LutNode = GetSolNode(&pair->measurements[i]);
if(LutNode) {
//if(CONF_YES == LutNode->cross) {
// pair->Fault.cross = 1;
//}
if(CONF_YES == LutNode->left_short_gnd) {
pair->Fault.left_short_gnd = 1;
}
if(CONF_YES == LutNode->left_short_vcc) {
pair->Fault.left_short_vcc = 1;
}
if(CONF_YES == LutNode->right_short_gnd) {
pair->Fault.right_short_gnd = 1;
}
if(CONF_YES == LutNode->right_short_vcc) {
pair->Fault.right_short_vcc = 1;
}
res = true;
}else{
LOG_ERROR(CROSS_DETECT,"UndefCase State %u, Read %u",state,pair->measurements[i].read);
}
break;
}
}
return res;
}
static bool cross_detect_measure(CrossDetectPairInfo_t* const pair) {
bool res = false;
if(pair) {
LOG_PARN(CROSS_DETECT, "MeasureGPIOin %s", CrossDetectState2Str(pair->state));
GpioLogicLevel_t logic_left=GPIO_LVL_UNDEF;
GpioLogicLevel_t logic_right=GPIO_LVL_UNDEF;
res = gpio_get_state(pair->left.byte, &logic_left);
res = gpio_get_state(pair->right.byte, &logic_right);
if(GPIO_LVL_HI == logic_left) {
if(GPIO_LVL_HI == logic_right) {
pair->measurements[pair->state].read = READ_L1_R1;
} else {
pair->measurements[pair->state].read = READ_L1_R0;
}
} else {
if(GPIO_LVL_HI == logic_right) {
pair->measurements[pair->state].read = READ_L0_R1;
} else {
pair->measurements[pair->state].read = READ_L0_R0;
}
}
pair->measurements[pair->state].state = pair->state;
}
return res;
}
/*UP->AIR->Down->Up*/
bool cross_detect_proc_one(uint8_t num) {
bool res = false;
LOG_PARN(CROSS_DETECT, "Proc:%u", num);
CrossDetectHandle_t* Node = CrossDetectGetNode(num);
if(Node) {
if(Node->on) {
/*Measure GPIO logic levels */
res = cross_detect_measure(&Node->pair);
if(res) {
LOG_PARN(CROSS_DETECT, "MeasureGPIOOk");
} else {
Node->pair.err_cnt++;
LOG_ERROR(CROSS_DETECT, "MeaseireGPIOErr");
}
CrossDetectState_t new_state = StateTableLookUpTable[Node->pair.state].state_new;
CrossDetectHandler_t action = StateTableLookUpTable[Node->pair.state].action;
if(action) {
res = action(Node);
}
res = cross_detect_state_set(&Node->pair, new_state);
if(res) {
LOG_PARN(CROSS_DETECT, "SetStateOk %u=%s", new_state, CrossDetectState2Str(new_state));
} else {
LOG_ERROR(CROSS_DETECT, "SetStateErr %u=%s", new_state, CrossDetectState2Str(new_state));
}
} else {
LOG_DEBUG(CROSS_DETECT, "Off %u", num);
}
} else {
LOG_ERROR(CROSS_DETECT, "NodeErr %u", num);
}
return res;
}
bool cross_detect_proc(void) {
bool res = false;
uint8_t ok = 0;
uint8_t cnt = cross_detect_get_cnt();
LOG_PARN(CROSS_DETECT, "Proc Cnt:%u", cnt);
for(uint32_t i = 1; i <= cnt; i++) {
res = cross_detect_proc_one(i);
if(res) {
ok++;
}
}
if(ok) {
res = true;
} else {
res = false;
}
return res;
}
Как видите код простой и все функции помещаются на один экран. Также код инвариантен к выбору микроконтроллера, чтобы он заработал вам надо просто написать драйвер GPIO. А именно функции чтения логического уровня и установки подтяжек напряжений.
Фаза 9. Отладка
Хорошо. Код мы написали. Но как получить отчет работы программного компонента cross-detect?
Обычно для тестов на пропай для каждой версии платы собирают отдельную сборку прошивки с суффиксом IO-Bang. Эта прошивка как раз и содержит компонент cross-detect. Также там есть UART-CLI для связи c внешним миром, а все пины сконфигурированы на вход.
Вот тут-то нам и пригодится интерфейс командной строки поверх UART. Про него есть отдельный текст https://habr.com/ru/articles/694408/. Соединяем плату и LapTop переходником USB-UART, открываем программу TeraTerm, нажимаем help и видим набор доступных команд компонента cross-detect.
Так как этот конечный автомат разрабатывался прежде всего для определения замыканий соседних пинов, то вот это первым делом и проверим. Как видно прошивка в run-time распознала установленную проводную перемычку P0.04-P0.05. По сути получилась прозвонка цепи подобно тому как это происходит в DMM.
Вот отчет в TeraTerm о том, что пин P0.24 замкнул на GND.
Вот регистрация короткого замыкания на VCC
Этот конечный автомат проверил 6 проводников каждый с каждым за 22 секунды.
Достоинства cross-detect
1++ Использование этого способа тестирования практически ничего не стоит. Всё можно написать с нуля за 3 дня.
2++Это чисто софтверный способ тестирования. Из оборудование нужен только копеечный переходник USB-UART.
3++ Программный компонент cross-detect позволяет определять все 4 типа возможных неисправностей:
№ |
Пример неисправности |
1 |
короткое замыкание на GND |
2 |
короткое замыкание на VCC |
3 |
короткое замыкание любых двух проводников на плате друг на друга |
4 |
обрыв нагрузки между пинами |
4++ Сross-detect может выявить короткое замыкание (КЗ) пинов прямо в BGA корпусах, где даже щупом осциллографа или pogo pins к пинам не подлезть! С BGA у Вас только два варианта: просвечивать плату с BGA рентгеновским сканером (X-ray) и глазами искать КЗ между пинами, либо прогонять сross-detect.
Что можно ещё улучшить?
На самом деле исходный код cross-detect вовсе не обязательно исполнять на target устройстве. Например на ARM Cortex-M4 или ARM Cortex-M33. cross-detect - просто алгоритм. Можно cоединить PC и Target PCB интерфейсом JTAG через микросхему переходник USB-JTAG (FT232H), написать на DeskTop консольное приложение симулятор прошивки с CLIшкой в stdin/stdout (или GUI) и прокручивать этот же самый конечный автомат опроса GPIO прямо на LapTop(е) NetTop(e).
По сути надо заменить CMSIS на код драйвера переходника USB-JTAG, чтобы читать и писать физическую память микроконтроллера. Дело в том, что через интерфейс JTAG можно получить тотальный доступ ко всем внутренностям микроконтроллера или микропроцессора (GPIO, SPI, PLL, RCC, UART и прочее).
При этом управляя микроконтроллером по JTAG прерывания лучше не запускать, так как иначе ваша программа должна будет также определить и таблицу векторов прерываний, как и сами обработчики ISR. То есть вам придется ещё вручную сделать то, что делает компоновщик (Linker).
Если говорить метафорами, то получается микроконтроллер-марионетка. Вместо куклы микроконтроллер, вместо ниточек 4 провода JTAG. А кукловод это Host PC.
По сути те же утилиты для прошивки по JTAG (например STM32 ST-LINK Utility.exe) так и работают. Они по JTAG подключаются к карте памяти микроконтроллера, обращаются к контроллеру Flash памяти, снимают защиту на запись, записывают по частям бинарь, включают обратно защиту на запись Flash и перезагружают ядро микроконтроллера.
Плюсы теста на пропай через JTAG
1++Утилиту Firmware Simulator можно писать абсолютно на любом моднявом языке программирования (Python, С#, C++, Go, Java, C).
2++вы никак не ограничены размерами бинаря. Это значит, что вы можете подключить базу данных и заносить отчеты о проверке изделия в базу знаний, генерировать логи, отрисовывать сложную графику на мониторе, делать запросы на сервера и использовать все ресурсы большого компьютера.
3++Также вы сможете протестировать пины самого UART до того как его впервые включат.
4++УJTAG высокая битовая скорость 30MHz. Это в 30--60 раз выше чем по UART
Вывод
Если говорить метафорично, то cross-detect это такие гномы, которые бегают по электронной плате и находят брак в производстве PCB. Вообще подтяжки напряжения просто идеально подходят для распознавания коротко-замкнутых проводов.
По сути плата тестирует сама себя изнутри.
Теперь и вы умеете пользоваться компонентом cross-detect и можете учить других. Надеюсь этот текст поможет программистам микроконтроллеров быстро и эффективно находить PCB брак в прототипах электронных плат.
Как видите благодаря этому остроумному алгоритму можно софтом определить замыкания на плате!
Словарь для понимания текста и комментариев
Акроним |
Расшифровка |
GPIO |
General-purpose input/output |
V |
Volts |
ISR |
Interrupt Service Routine |
CMSIS |
Common Microcontroller Software Interface Standard |
CLI |
command-line interface |
ОТК |
Отдел технического контроля |
UART |
universal asynchronous receiver / transmitter |
PCB |
printed circuit board |
КЗ |
короткое замыкание |
BGA |
Ball grid array |
DFM |
design for manufacturability |
FSM |
Finite-state machine |
MPSSE |
Multi-Protocol Synchronous Serial Engine |
EoL |
End-of-life |
MCAL |
MicroController Absorption Layer |
DMM |
digital multimeter |
МК |
Микроконтроллер |
MCU |
Micro Controller Unit |
GCC |
GNU Compiler Collection, GNU-GNU’s Not UNIX |
UNIX/UNICS |
Uniplexed Information and Computing System |
ЛУТ |
Лазерно-утюжная технология изготовления печатных плат |
IO |
Input/Output |
JTAG |
Joint Test Action Group |
NDA |
Non-disclosure agreement |
SWD |
Serial Wire Debug |
ПО |
Программное обеспечение |
ОС |
operating system |
ЭВМ |
Электронная вычислительная машина |
Links
Load-Detect для Проверки Качества Пайки https://habr.com/ru/articles/756572/
H-мост: Load Detect (или как выявлять вандализм) https://habr.com/ru/articles/709374/
Все таблички из текста можно просмотреть тут https://docs.google.com/spreadsheets/d/1Bs4YaRxqCRQDz73HKJhUfD-evO-U6rQLS0fySpSOWG8/edit#gid=2020010813
Что такое UART-CLI https://habr.com/ru/articles/694408/
Контрольные вопросы:
1--Как выявить короткое замыкание между соседними пинами в уже припаянной BGA микросхеме?
2--Электронная плата с производства не работает. Что Вы предпримете чтобы выявить причину поломки?
3--Чем конечный автомат Мура отличается от конечного автомата Мили?
Комментарии (113)
aystarik
23.09.2023 15:07+3а почему не JTAG + boundary scan?
aabzel Автор
23.09.2023 15:07-2Есть где методичка как пользоваться этим JTAG + boundary scan ? Какое нужно оборудование, какой софтвер?
Ну поставит программатор в boundary scan на пине P0.04 3.3V, а дальше-то что?
boundary scan применим только тогда когда у тебя на плате 2+ микросхем с JTAG интерфейсом.
А в микроконтроллерных проектах только одна JTAG микросхема (микроконтроллер).
Можно что угодно ставить на GPIO через JTAG, а толу-то?
И вообще на большинстве электронных плат с MCU на разъёмы выводят не JTAG, а двухпроводной интерфейс SWD.aystarik
23.09.2023 15:07+2https://fiona.dmcs.pl/~rkielbik/nid/IEEE_1149_JTAG_and_Boundary_Scan_Tutorial.pdf
SWD это только провода, boundary-scan по ним тоже доступен: https://www.xjtag.com/company/newsroom/single/press-releases/xjtag-introduces-arm-swd-support-and-user-defined-libraries/
aystarik
23.09.2023 15:07+1https://github.com/viveris/jtag-boundary-scanner --вот здесь уже софт какой-то есть...
aystarik
23.09.2023 15:07+1aabzel Автор
23.09.2023 15:07-3
Лучше бы господа Кауркин Максим Николаевич , Стариковский Алексей Юрьевич , Гурин Константин Львович написали бы пост на habr про свой уникальный опыт управления процессором по JTAG чем заполнять эти мнимые конторские свидетельства.
Тогда бы их труд хоть кто-то реально прочитал и прокомментировал.
Наверное еще текст своих исходников на миллиметровой бумаге печатали чтобы их рассмотрели.aystarik
23.09.2023 15:07+7Стариковский Алексей Юрьевич перед вами, спрашивайте, что вас интересует...
aabzel Автор
23.09.2023 15:07--Какое устройство выступало в роли переходника с USB-JTAG? Подойдет ST-Link V2 ISOL?
--Какой был программный API для управления отладчиком на стороне PC приложения?
--Как обрабатывались аппаратные прерывания по переполнению таймера на target?
aabzel Автор
23.09.2023 15:07+1Почему нельзя было воспользоваться для этого не JTAG, а UART?
Можно же написать прошивку для процессора с UART-CLI, где будет всего 2 команды: чтение физического адреса и запись физического адреса.
Залить прошивку в ROM процессора.
Далее соединить PC и PCB переходником USB-UART и с тем же успехом утилитой на PC читать и писать регистры. Интерпретировать их по datasheet и отрисовывать красивую GUI.
Переходники USB-UART они же дешевле чем переходники USB-JTAG.aystarik
23.09.2023 15:07+5потому, что это "взрослый" чип, у него память -- это DDR3 снаружи, а через JTAG мы могли получить доступ ко всему, даже при полностью мертвом процессоре. FT232 стоит не сильно дороже FT230, а если вместе с платой, то разницы уже не видно. JTAG можно до 30+МГц разогнать, UART так не умеет.
aabzel Автор
23.09.2023 15:07Получается что если делать то же только для MCU, то можно на PC написать внешнюю прошивку размером с гигабайт в бинаре, которая никогда бы не поместилась бы во on-chip Flash память.
Вот только не ясно откуда микроконтроллер будет исполнять код прерываний (внешнее прерывание по нажатию кнопки) если прошивка внешняя и крутится на PC.
Из on-chip RAM памяти что-ли?
Тогда PC еще должен будет исполнять роль и компоновщика и интерпретатора одновременно.aystarik
23.09.2023 15:07+2вы зачем-то мешаете в одну кучу отладку переферии и проверку внешних соединений с отладкой программы на ядре контроллера. Первое можно делать внешней программой через прямой доступ к регистрам устройств и к ножкам по boundary-scan. отладку программы в ядре (прерывания) уже можно отлаживать с участием собственно ядра... И откуда берутся "гигабайты в бинаре", сколько функциональности хотите, столько и будет размера...
aabzel Автор
23.09.2023 15:07Ясно.
Берем прошивку. Меняем CMSIS на API управления USB-JTAG и прокручиваем всё тот же cross-dectct.
Только не на Target, а на PC.
aabzel Автор
23.09.2023 15:07+1что это "взрослый" чип
Даже в CPU процессорах Cortex-A есть несколько десятков килобайт для on-chip BOOT ROM(а). Например 1892ВМ14Я
aystarik
23.09.2023 15:07+2и? в Байкал-Т тоже был BOOT ROM MONITOR, доступный через консоль. Только вот что делать, если консоль не отвечает?
aabzel Автор
23.09.2023 15:07Значат что-то с проводами, либо прошивка на target зависла.
Мне в микроконтроллерах JTAG нужен только для пошаговой отладки, в случае если CLI не отвечает на команды из TeraTerm.
Беру очередной MCU, настраиваю на нем UART-Shell, а далее отладка идет только по UART-CLI
У меня есть отдельный текст про то, почему нужна консоль в UART
https://habr.com/ru/articles/694408/
aystarik
23.09.2023 15:07+6настраиваю на нем UART-Shell
т.е. вам нужно в слепую подать питание на HSE, PLL, GPIO, UART, выставить частоту PLL, wait-states для FLASH, ну и бауд-рэйт UART... ошибка в одной операции -- и у вас пересборка-перезаливка. Альтернатива -- все это понастраивать снаружи и заливать уже только рабочий вариант...
aabzel Автор
23.09.2023 15:07все это понастраивать снаружи и заливать уже только рабочий вариант.
Вот только как отладить на PC программные компоненты MCU, которые вызывают внешние прерывания?
В микроконтроллерах у каждого блока (GPIO RCC Timer SysTick Flash UART DMA) есть целая куча всяческих прерываний на каждый чих.
И исполняться ISR прерывания должны либо из on-chip RAM либо из on-chip Flash.
aabzel Автор
23.09.2023 15:07Если говорить метафорами, то получается микроконтроллер-марионетка.
Вместо куклы микроконтроллер, вместо ниточек 4 провода JTAG. А кукловод это Host PC.
aabzel Автор
23.09.2023 15:07-1То есть у вас на PC работает одновременно GdbServer и ваша GUI утилита.
GdbServer общается по JTAG c Target, а ваша утилита по TCP сокету общается с GdbServer .
GUI утилита управляет GPIO через GdbServer сервер. Так?
makkarpov
23.09.2023 15:07+1JTAG/SWD дают прямой доступ к памяти МК, и, как следствие, ко всем его регистрам. Не очень быстрый, но доступ. Не только к GPIO, а вообще ко всей периферии. Хоть GPIO ставьте, хоть АЦП считывайте, хоть через I2C с остальной платой разговаривайте. Сам boundary scan (в своем изначальном смысле) - это исключительно JTAG, но тут он и не нужен.
Всё то же самое можно реализовать на хостовой стороне, получив в добавок кучу бонусов - например, (теоретическую) возможность использовать одну и ту же софтину для кучи плат, задавая конкретную конфигурацию хоть мышкой. Да и сама софтина может быть хоть десять гигабайт, уметь тестировать не только сам МК, но еще, например, периферийные микросхемы.
aabzel Автор
23.09.2023 15:07JTAG/SWD дают прямой доступ к памяти МК, и, как следствие, ко всем его регистрам. Не очень быстрый, но доступ. Не только к GPIO, а вообще ко всей периферии. Хоть GPIO ставьте, хоть АЦП считывайте, хоть через I2C с остальной платой разговаривайте.
Это что мне надо на PC писать отдельный симулятор прошивки под Win и весь MCAL, чтобы через JTAG GPIO считывать/прописывать?
Да это же жесть полнейшая. Cross-Detect в 100 крат проще получается.makkarpov
23.09.2023 15:07+2Ну, приведенное вам по ссылке коммерческое ПО именно так и работает.
Учитывая, что в приведенном вами фрагменте "прошивки" 10% логики и 90% простыни из копипаста - уверен, на каком-нибудь python это получилось бы куда проще как минимум из-за замены C высокоуровневым языком, а так же доступности полноценной ОС. Для того, чтобы через SWD дергать ножками GPIO, усилий вообще не требуется (помимо поиска библиотеки для используемого отладчика).
aabzel Автор
23.09.2023 15:07-1уверен, на каком-нибудь python это получилось бы куда проще как минимум из-за замены C высокоуровневым языком, а так же доступности полноценной ОС. Для того, чтобы через SWD дергать ножками GPIO, усилий вообще не требуется (помимо поиска библиотеки для используемого отладчика).
В Роcсии никто так не делает!
Писать на Python симулятор прошивки который работает на Windows. При этом поддерживать Real-Time SWD link с target(ом) чтобы на реальной железке отражались изменения на PCB.
Вы попробуйте убедить Python разработчика чтобы он перешел из Back-end разработки на Python за 300kRUR заниматься таким мартышкиным трудом в embedded за 50 kRUR.
Это просто смешно!segment
23.09.2023 15:07+4Смешно то, что Вы считаете, что никто так не делает. Вам все правильно разъясняют, и python был приведен как пример, можете брать тот же C/C++.
aystarik
23.09.2023 15:07aabzel Автор
23.09.2023 15:07-5
Подразумеваю, что эти трое господ еще свои исходники на миллиметровой бумаге распечатали, чтобы получить это бессмысленное свидетельство.
Лучше бы они пост на habr написали про свой опыт управления процессором по JTAG.
Тогда бы их труд хотя бы один раз прочитали, оценили и прокомментировали.
aabzel Автор
23.09.2023 15:07+1В 2013 в военном НИИ меня тоже вовлекли в процесс получения государственной регистрации моей программы для ЭВМ. Я тогда обрадовался. Но зря.
Замучился распечатывать листы с исходниками. При этом приписался целый поезд из чинуш-соавторов у которых на столе даже компьютера никогда не стояло.
В результате никто текст так и не прочитал и обратной связи не было как и вознаграждения тоже.
В итоге я понял одну вещь.
Лучшее что можно сделать со своей хорошей программой - это написать по нее пост на habr.aystarik
23.09.2023 15:07+1NDA никто не отменял... А еще и секретность временами приплетают... лучше всего иметь код на каком-нибудь github, там разговор предметнее получается.
kipar
23.09.2023 15:07+1не совсем понятно зачем симулятор прошивки. Можно читать по jtag регистр состояния ножек и писать в регистр выхода ножек. А дальше запускаем тот же алгоритм сканирования перебором, но уже на компе.
aabzel Автор
23.09.2023 15:07Нужно еще регистр установки подтяжек читать писать.
А вот выход ножек трогать нельзя. Это же push-pull.
В случае КЗ на GND push-pull (ом) можно нечаянно сжечь плату.
aabzel Автор
23.09.2023 15:07+1Можно читать по jtag регистр состояния ножек и писать в регистр выхода ножек.
Осталось только понять какой там API, чтобы из консольного windows приложения на Си читать регистры MCU по JTAG.
И нужеy ли параллельно работающий GDBServer (ST-LINK_gdbserver.exe) для этого?
aabzel Автор
23.09.2023 15:07JTAG/SWD дают прямой доступ к памяти МК, и, как следствие, ко всем его регистрам. Не очень быстрый, но доступ. Не только к GPIO, а вообще ко всей периферии. Хоть GPIO ставьте, хоть АЦП считывайте, хоть через I2C с остальной платой разговаривайте.
Всё то же самое можно реализовать на хостовой стороне, получив в добавок кучу бонусов - например, (теоретическую) возможность использовать одну и ту же софтину для кучи плат, задавая конкретную конфигурацию хоть мышкой. Да и сама софтина может быть хоть десять гигабайт, уметь тестировать не только сам МК, но еще, например, периферийные микросхемы.
А как тогда обрабатывать внешние прерывания (например по SysTick таймеру или GPIO, I2C, UART, DMA, SPI)? Тоже на Host(e)?
makkarpov
23.09.2023 15:07+1Вам нужно работоспособность SysTick таймера протестировать (вдруг чип бракованный подсунули?) или корректность сборки платы?
Если вдруг вам часто подсовывают чипы, где сломан SysTick таймер - можете залить тестовый код в RAM и оттуда выполнить. Но это мягко говоря нецелевое применение обсуждаемой технологии.
aabzel Автор
23.09.2023 15:07-2а почему не JTAG + boundary scan?
Обыкновенный инженер может проверить что-то только одним единственным способом, хороший - тремя способам.aystarik
23.09.2023 15:07+12Вы привели один способ и всячески отбиваетесь от других способов... Вы к какой категории себя после этого относите?
Kudriavyi
23.09.2023 15:07+4Я наблюдал за автором статьи и не рекомендую вам с ним спорить - затея абсолютно бесполезная. Он пропагандирует отладку через uart cli и других способов не приемлет от слова совсем. А кто этого не понимает - тот чернь неученая. Так же уже писали, что блок-схемы автора перегружены и в них без стакана не разберёшься, но это тоже все потому, что никто не понимает истины
Причём я не хочу очернять подходы автора данной статьи, но у него совершенно отсутствует критика к себе, что сильно снижает рейтинги /то есть одобрение читающих/
makkarpov
23.09.2023 15:07+2Ваша основная проблема состоит в том, что вы делаете какую-то вещь под себя, после чего приходите на хабр и начинаете убеждать народ, что это правильный и универсальный подход, и весь мир завтра так должен делать. И на критику реагируете фразой "Это что мне надо ...". Нет, вам ничего не надо. Разговор вообще не идет о том, как оптимизировать ваши процессы. Разговор идет о том, как оптимальнее решить задачу целиком, причем для случайного мимокрокодила.
У меня, например, нет готового UART CLI (и никогда не появится, потому что он мне нафиг не нужен, это пустой расход как человеческих ресурсов, так и машинных). Думаю, что у большинства читателей тоже.
Специально для вас прикладываю скриптик в 40 строк, который читает и пишет любые регистры на реальном МК. Даже светодиодик загорается, лично проверил. Никаких проблем добавить туда хоть CLI, хоть HTML вообще нет - и для этого не надо писать никаких драйверов UART-а, достаточно простого
println
.Душат змею без регистрации и SMS
import time import swd class GpioTest: RCC_AHB2ENR1 = 0x46020C8C RCC_AHB2ENR1_GPIOBEN = 1 << 1 GPIOB = 0x42020400 GPIO_MODER = 0x00 GPIO_ODR = 0x14 MODE_OUTPUT = 0x1 def __init__(self): self.swd = swd.Swd() self.cortex = swd.CortexM(self.swd) def _modify_reg(self, addr: int, b_reset: int, b_set: int): self.swd.set_mem32(addr, (self.swd.get_mem32(addr) & ~b_reset) | b_set) def _set_mode(self, gpio: int, bit: int, mode: int): self._modify_reg(gpio + GpioTest.GPIO_MODER, 3 << (2 * bit), mode << (2 * bit)) def _set_pin(self, gpio: int, bit: int, value: bool): self._modify_reg(gpio + GpioTest.GPIO_ODR, 1 << bit, int(value) << bit) def run(self): self.cortex.reset_halt() while not self.cortex.is_halted(): time.sleep(0.1) self._modify_reg(GpioTest.RCC_AHB2ENR1, 0, GpioTest.RCC_AHB2ENR1_GPIOBEN) self._set_mode(GpioTest.GPIOB, 7, GpioTest.MODE_OUTPUT) self._set_pin(GpioTest.GPIOB, 7, True) if __name__ == '__main__': GpioTest().run()
aabzel Автор
23.09.2023 15:07+1У меня, например, нет готового UART CLI
Если кому-то еще нужен код UART-CLI пишете в личку свои email/TG и я пришлю свою версию UART-CLI сорцов. Уже пару человек обращались. Выслал без проблем.
aabzel Автор
23.09.2023 15:07+1Зачем вы пишите этот тестировочный код на Python?
Не лучше ли бы написать его на С или С++? Тогда бы код был переносимым.
Вы могли бы запускать тест GPIO так на target(е) так и на PC через переходник USB/JTAG, просто заменив CMSIS на драйвер переходника USB/JTAG одной строчкой в makefile.makkarpov
23.09.2023 15:07Выглядит так, как будто исчерпывающий ответ на это был дан в моих прошлых комментариях. Код написан на Python из-за того, что на таргете его запускать априори не планируется по самой постановке задачи.
aabzel Автор
23.09.2023 15:07Код написан на Python из-за того, что на таргете его запускать априори не планируется по самой постановке задачи.
Писать код для JTAG адаптера на Python это мартышкин труд!
Такие вещи надо на C или C++ делать, чтобы и разработчики прошивки и тестировщики могли использовать одну общую протестированную пере используемую кодовую базу.
Тем более стандарт автомобильного ПО ISO26262 запрещает использовать в разработке языки без строгой типизации типов данных (таких как Python) для всех ASIL.
aabzel Автор
23.09.2023 15:07+1а почему не JTAG + boundary scan?
Разные методы тестирования и отладки не исключают друг друга, а дополняют.
RV3EFE
23.09.2023 15:07-1Хорошая статья. Интересный подход, который имеет право на жизнь. Видно, что автор участвует и имеет опыт в разработке и производстве серийного оборудования, с подходом экономичного производство( не путать с бережливым (5s)), а не как 90% разработчиков, только прототипов, которые идут в ящик или производятся штучно.
MagNitronik
23.09.2023 15:07+2В качестве учебной задачи очень неплохо. Для производства конечно не подходит. Огорчает, что автор не разобрался с Boundary-scan. Существуют серийные образцы jtag- сканеров, в ПО которых загружается схемотехника платы, что дает возможность учитывать поведение других компонентов платы.
aabzel Автор
23.09.2023 15:07-4Огорчает, что автор не разобрался с Boundary-scan. Существуют серийные образцы jtag- сканеров, в ПО которых загружается схемотехника платы, что дает возможность учитывать поведение других компонентов платы.
Огорчает то, что Вы @MagNitronik
не написали по это текст на habr, раз у Вас есть экспертиза в этой теме.MagNitronik
23.09.2023 15:07+3Это мало кому интересно. У тех, кто купил проф. Оборудование есть на него мануал.
Вас обижает, что вас не поняли?
devprodest
23.09.2023 15:07+2Не всё упирается в статьи на Хабре: одним это нафиг не интересно писать, у других нет времени.
ИМХО, проблемы серийных плат это проблемы производства и отк. В штучных прототипах эти вопросы легко решаются вместе со схемотехниками.
Статья же переносит проблемы с больной головы на здоровую.
aabzel Автор
23.09.2023 15:07Не всё упирается в статьи на Хабре: одним это нафиг не интересно писать, у других нет времени.
Написание постов на habr это своего рода вариант для сохранения ценной документации по проекту для тех компаний у которых нет денег на Atlassian Confluence(а).
Мне часто приходилось самому читать свои же старые тексты на habr так как там была инструкция того что приходится делать снова и снова в разное время.
https://habr.com/ru/articles/709932/
https://habr.com/ru/articles/695978/
https://habr.com/ru/articles/694708/
https://habr.com/ru/articles/683762/
https://habr.com/ru/articles/735422/
https://habr.com/ru/articles/726352/
https://habr.com/ru/articles/682498/
https://habr.com/ru/articles/673522/
Плюс хорошие комменты позволяют улучшить инструкцию до более понятного вида.devprodest
23.09.2023 15:07+1Для хранения информации о проекте есть куча бесплатных википодобных систем.
К тому же не всё можно и нужно постить в публичных местах.
Я вас не отговариваю, пишите если нравится.
vit496
23.09.2023 15:07+2I2C не работает нормально на внутренних подтяжках. А если поставить внешние резисторы 4.7к, как положено, то не сработает этот тест.
mlnw
23.09.2023 15:07С 5В МК, работающим с 3.3В периферией в режиме открытого коллектора, такая эквилибристика с подтяжками просто пожжет периферию.
HEXFFFFFFFF
Зачем так сложно о простом? Конечно программно можно выявить некоторые косяки в платах, но далеко ни все и не всегда. Замыкание или непропай может быть в каком то таком месте что проц даже и не запустится, а может быть в каких то аналоговых сетях и МК не сможет это проверить. Пытаться софтом определить замыкания на плате бесполезное занятие.
Гораздо эффективней (и я всегда так делаю) написать тестовую прошивку которая проверяет всю переферию. Это может быть отдельная программа , а может быть модуль в основном софте запусаемый каким то хитрым образом. Он же может использоватся и при последующим ремонте.
aabzel Автор
Этот программный компонент реально работает и уже несколько раз помог найти КЗ в платах.
aabzel Автор
Вот вам пример из жизни. При инициализации шины I2S микроконтроллер перезагружается. На другой плате этого не происходит. Как вы поймете какой из 5х проводов шины I2S коротит на UART TX?
cujos
а зачем определять? выглядит как брак
ремонтировать после EoL немного не корректно, нужно работать с отделом качества контрактников
кмк вещь полезная, чтобы браковать, но зачем понимать какой из проводов куда коротит?
проверил функционал - брак/не брак и достаточно
aabzel Автор
Разработчику надо не только выявить факт брака, но также надо распознать причину брака.
Это нужно чтобы объяснить тётушкам на производстве, что надо отпаять и что надо заменить максимально понятно, чтобы они вас поняли и починили.
cujos
причина в некачественной пайке, этим должен заниматься тот, кто паял
как я уже сказал осуществлять ремонт после того, как он прошел контроль не корректно
разве что на единичных сборках/мелких сериях/прототипах, у меня вот лежит куча с браком, раньше процент был больше, со временем уменьшается, но стабильно пара процентов уходит: убивает что-то статикой, непропай, повышенное потребление, выходит за расчет аналоговых параметров - не важно
разбираться, а уж тем более менять что-то после автоматического монтажа - бред
долго, дорого и не качественно
aabzel Автор
Ну вот включил программист-микроконтрллеров плату и плата не работает. Разработчик идет в цэх, подходит к монтажнице и говорит:
-плата которую ты собрала не работает
монтажница в ответ:
--Ок, что именно не работает и что мне надо поменять?
Разработчик прикидывается валенком и говорит:
--Да я особо не разбирался. Не знаю. Смотрю не работает и принес тебе.
Так что-ли надо работать по Вашему?
iig
Что может поменять монтажница в ручной пайка BGA?
aabzel Автор
Вот cross-detect как раз и локализует аппаратный баг.
Далее будет уже 1, 2 варианта решения аппаратной ошибки.
aabzel Автор
Вот нет никакого контроля. Плату спаяли тётушки бальзаковского возраста, визуально глазками посмотрели на PCB (ничего такого не заметили) и передали программисту-микроконтроллеров.
Программист-микроконтроллеров прошил плату релизной прошивкой и видит, что плата не работает.
Что делать?
cujos
отдавать на нормальный монтаж
aabzel Автор
Ошибки не только в монтаже бывают, но и в самом дизайне PCB тоже.
Вот Вам яркий пример.
Схемотехники для RS2058 взяли для Э3 распиновку от корпуса MSOP10, а на PCB поставили корпус QFN.
В результате мультиплексор не пропускает сигнал так как распиновка в MSOP10 и QFN не совпадает на 90%.
Никто на этаже не мог понять что не так.
Зато мой еще прошлый примитивный load-detect
https://habr.com/ru/articles/756572/
выявил место проблемы на первой же секунде своей работы.
cujos
ну тогда все не будет работать, их нужно до серии выявлять
я же говорю: в принципе штука полезная, но для выходного контроля достаточно выводов годная или нет конкретная плата
если нет - то в брак, если брака слишком много - на анализ, но вот тестировать и ремонтировать серийное изделие после сборки - неправильно
aabzel Автор
Для серии обязательно надо строить полноценные test jig(и). Типа таких
https://thirdpin.io/stendy_testirovaniya_elektroniki_i_pechatnyh_plat
Cross-detect хорош на стадии проверки первого, второго прототипа PCB. Когда еще нет ни одной test jig и окончательного решения о запуске массового производства.
aumi13
неправильно это создавать электронный мусор, если есть возможность отремонтировать. другой вопрос что ремонтировать возможно некому.
Karlson_rwa
А это системная ошибка. При грамотно построенной разработке такого не должно быть в принципе. Если такое случается, то это означает, что процесс проектирования электроники построен неверно.
Бегите из этого места как можно скорее. :)
aabzel Автор
Куда бежать? В РФ везде так.
Karlson_rwa
Я вам уже писал, не стоит распространять свой опыт на всю индустрию целиком. Если вам по работе попадались только гнилые места, это не означает, что все места такие.
Карась плавает. Карась — рыба. Но это не означает, что все рыбы плавают. А вы продолжаете это упорно утверждать из раза в раз.
randomsimplenumber
Бальзак, "Тридцатилетняя женщина". Что за эйджизм ;)
Так то у вас получился аналог автотестов, но для железа. Если тесты прошли - это ничего не значит. Если упали - значит ошибки точно есть.
iig
Элементарно, Ватсон ;)
Тот который рядом.
Напряжометр с пищалкой в помощь ;)
aabzel Автор
Это потом выяснилось что I2S c UART коротит.
Cross-deteсt как раз показал.
Сначала вообще не ясно было что не так. Тем же DMM прозванивать - это бы целый день заняло а, то и неделю.
А сross-deteсt все нежелательные перемычки за минуту найдет
iig
Нет смысла проверять КЗ любых 2 точек на плате. Имеет смысл проверять близко расположенные, и непосредственно связанные с МК. Вряд ли это займет весь день.
Хотя, конечно, автоматизированное тестирование найдет ошибку бесплатно (если ошибка действительно связана с КЗ ножек МК).
aabzel Автор
У программистом MCU на компе нет топологии всех 4х внутренних слоев PCB, чтобы понять кто там рядом проложен, а кто нет.
Да и тест падов на PCB тоже нет, чтобы приложить электроды DMM для прозвонки.
iig
Если закорочены внутренние слои, тут, пожалуй, какие вопросы к монтажникам?
aabzel Автор
Программисты тем более вообще имеют право ничего не знать о топологии.
aabzel Автор
Тогда плату надо тестировать еще до того как на нее компоненты будут припаяны. Нужно для каждой PCB test jig строить.
iig
Вроде как производство печатных плат достаточно стабильно. Вы же не ЛУТ практикуете дла 4-слойных плат?
aabzel Автор
Пробовал ЛУТ для однослойки. Слишком много мороки и брака.
segment
Ээ, так и тестируют платы, если того требует сложность платы. Для этого есть тот же flying probe.
aabzel Автор
В Росcии есть хоть одна фирма, которая разработала собственное оборудование для flying probe ?
segment
Мне кажется тут не так важно есть ли собственное оборудование или нет, тк в рф оно вряд ли появится в скором времени. Тот же Резонит предоставляет электроконтроль через fly probe уже давно.
aabzel Автор
Видимо Вы ещё очень мало времени занимаететь программированием микроконтроллеров.
aabzel Автор
Где Вы тут сложность-то увидели? Конечный автомат на 9 состояний для Вас сложно?
aabzel Автор
Что дешевле: строить для каждой платы тестировочный стенд с pogo pins
или залить прошивку IoBang с Cross-detect(ом)?
segment
Test jig необходим в подавляющем большинстве случаев, если речь идет именно о промышленном производстве, а не локальном кустарном. Да даже для небольших партий чего бы то ни было все-равно нужен стенд с оснасткой для проверки.
aabzel Автор
Как Вы собираетесь pogo pin(ами) от test jig подбираться пинам BGA микросхем , чтобы выявить короткое замыкание на соседних пинах?
segment
Зависит от контекста. В случае производства закладывается процент брака, отбракованные платы откладываются и потом их проверяют вручную, и, скорее всего, при небольшом проценте брака такими платами никто не будет заниматься. Ну отправить на проверку стоит конечно для определения в чем был брак, но такую отремонтированную плату вряд ли будут пускать обратно в производственный процесс, если только это не что-то очень дорогое. В ручном тестировании, если определиться что где-то коротит — ну перепаяют подозрительные чипы. И зачем заводить какой-то софт, который сможет всего лишь частично определить проблемное место?
aabzel Автор
Чтобы в следующей ревизии оптимизировать трассировку PCB.
segment
Если что-то не так с трассировкой это либо выявится на стадии отладки и прототипов, либо при получении большого процента брака. И в этих случаях плата отдается на ручное тестирование и анализ. На мой взгляд решаемая задача высосана из пальца.
Perycalypsis
Давно изобретен автоматический визуальный и рентген контроль на производстве пп и монтаже. Если требования DFM выполнены, то все работает. После приемки нужен только функциональный контроль - работает или нет через тестовую прошивку или часть загрузчика. Брак в помойку или возврат на производство, когда накопится. А при отладке пары плат это делается ручками и глазами + рентген для бга.
По вашим описаниям прцесса - чую я гнилую НИИ шную организацию работы, когда люди не знакомы с мировыми практиками и стандартами и вынуждены изобретать костыли. Сам 20 лет там отработал и все это проходил.
Karlson_rwa
Для этого есть JTAG.
a9d
собрать тестинг джиг стоит где-то 150$. Без него один хрен в серию ничего не запустить. На производстве же завернут и пошлют лесом
aabzel Автор
Вот Вам механизм попроще Load-Detect для Проверки Качества Пайки
https://habr.com/ru/articles/756572/
но он не умеет определять короткие замыкания между дорожками PCB. Зато просто.