Во всех современных микроконтроллерах уже давно как (больше 11 лет) есть подтяжки напряжения на пинах GPIO. Как вы думаете зачем в микроконтроллерах есть функция pull-up/pull-down, если можно просто воспользоваться установкой логического уровня push-pull?

Вы наверное скажете, что подтяжки к питанию нужны для конфигурации пинов шины I2C/1-Wire, нужны для кнопок. Верно! Но это не единственная причина.

Вот типичная ситуация. Вам принесли 6ти слойную электронную плату прямо с производства. Её ещё ни разу не включали. Обычно в таких случаях 90% вероятность, что в PCB есть какие-то аппаратные баги: короткие замыкания на GND, короткие замыкания на VCC или вовсе непропай пинов MCU. Как выявить эти бракованные пины?

Вот тут-то нам и помогут подтяжки к питанию и земле на пинах MCU. Называется эта тема load-detect (LD). У меня уже был текст про load-detect для тестирования силовых высоковольтных H-мостов перед запуском. Вот он: «H‑мост: Load Detect (или как выявлять вандализм)».

Однако load-detect можно реализовать не только на специализированных для этого ASIC(ах), а прямо на пинах микроконтроллера!

LD может выявить короткое замыкание (КЗ) пинов прямо в BGA корпусах, где даже щупом осциллографа к пинам не подлезть! LD это часть прошивки, чисто программный компонент.

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

Обычно LD оформляют как конечный автомат на три состояния. Разработка же конечных автоматов это хорошо формализованный процесс, состоящий из 7 фаз.

Фаза 1. Перечислить возможные состояния конечного автомата

#

Пояснение

Состояние

1

На пине нет подтяжек напряжения

Pull air

2

На пине подтяжка к GND

Pull GND

3

На пине подтяжка к VCC

Pull VCC

Фаза 2. Определить входы конечного автомата

В данном случае у конечного автомата будет только один вход. Это сигнал переполнения таймера. TimeOut. Дело в том что при установке подтяжки напряжения надо подождать окончания переходного процесса и только потом измерять состояние логического уровня на пине микроконтроллера. Обычно это время порядка единиц миллисекунд.

В частности же время переходного процесса на пине не превышает даже 3 миллисекунд. Получается, что можно обновлять состояние диагностики пина с периодом 4ms*3 = 12 ms или с частотой 83 Hz!

Фаза 3. Определить действия конечного автомата

Конечный автомат контроля пайки может делать только следующие легальные действия

#

Пояснение действия

Действие

1

Измерить логический уровень на пине

Read GPIO

2

Установить на пине подтяжку к питанию

Set pull Up

3

Установить на пине подтяжку к заземлению

Set pull Down

4

Отключить на пине какие - либо подтяжки

Set pull air

5

Вычислить решение о состоянии пина на основе накопленных измерений

calculate solution

Тут сразу надо отметить что такое вычисление решения. Вот Look Up таблица принятия решения по измерениям автомата Load detect. Как видно по��ле одного цикла измерений согласно комбинаторному правилу перемножения может быть максимум 8 различных вариантов (2*2*2 =2**3=8). В ячейках таблицы измеренные логические уровни GPIO пина на котором работал LoadDetect.

Open load означает, что пин ни к чему не подключен или, если формально, подключен к резистору с бесконечным сопротивлением одним концом и на GND другим. Еще говорят Z-состояние. И то же самое с VCC.

Фаза 4. Составить таблицу переходов для состояний конечного автомата

Как работать с этой таблицей. Если автомат был с состоянии pull-air и сработало прерывание по переполнению таймера, то автомат переходит в состояние pull-down. Если автомат был с состоянии pull-down и сработало прерывание по переполнению таймера, то автомат переходит в состояние pull-up. Если автомат был с состоянии pull-up и сработало прерывание по переполнению таймера, то автомат переходит в состояние pull-air. Это один цикл измерений. Дальше автомат продолжает работать непрерывно новые и новые циклы.

Фаза 5. Нарисовать граф переходов конечного автомата

На самом деле всё, что я тут написал можно объяснить только одной вот этой картинкой графа конечного автомата. Надо пробежаться по всем трём подтяжкам, в каждой подтяжке прочитать состояние пина и найти в подсказке строчку, которая соответствует этим измерениям и покажет в соседней колонке, что, собственно, подключено к этому конкретному пину со стороны улицы. Easy!

Это классический пример конечного автомата Мили так как выходы генерируются на основе входа и состояния.

Фаза 6. написать программный Си-код

Прежде всего LD надо сконфигурировать. Указать с какими пинами ему надо работать а с какими не надо.

#include "load_detect_config.h"

#ifndef HAS_LOAD_DETECT
#error "Add HAS_LOAD_DETECT"
#endif /*HAS_LOAD_DETECT*/

#include "data_utils.h"
#include "gpio_drv.h"
#include "log.h"


const LoadDetectPinConfig_t LoadDetectPinConfig[] = {
    {.num = 1, .pin_num = 1, .pad={.port=0, .pin=8}, .valid=true,},
    {.num = 1, .pin_num = 2, .pad={.port=0, .pin=16}, .valid=true,},
   ....
    {.num = 1, .pin_num = 22, .pad={.port=1, .pin=12}, .valid=true,},

};

LoadDetectPinInfo_t LoadDetectPinInstance[] = {
        {.num = 1, .pin_num = 1,  .valid=true,},
        {.num = 1, .pin_num = 2,  .valid=true,},
...
        {.num = 1, .pin_num = 22,  .valid=true,},
};

const LoadDetectConfig_t LoadDetectConfig[] = {
    {.num = 1, .name="MCUgpio", .valid=true,  .gpio_class=GPIO_CLASS_MCU, },
};

LoadDetectHandle_t LoadDetectInstance[] = {
    {.num = 1, .valid=true, },
};

uint32_t load_detect_get_cnt(void) {
    uint32_t cnt = 0;
    uint32_t cnt_conf = ARRAY_SIZE(LoadDetectConfig);
    uint32_t cnt_ints = ARRAY_SIZE(LoadDetectInstance);
    if(cnt_conf == cnt_ints) {
        cnt = cnt_ints;
    }
    return cnt;
}

uint32_t load_detect_get_pin_cnt(void) {
    uint32_t cnt = 0;
    uint32_t cnt_conf = ARRAY_SIZE(LoadDetectPinConfig);
    uint32_t cnt_ints = ARRAY_SIZE(LoadDetectPinInstance);
    if(cnt_conf == cnt_ints) {
        cnt = cnt_ints;
    }else{
    	LOG_ERROR(LOAD_DETECT,"PinConfigMisMatch ConfPins%u!=RamPins%u",cnt_conf,cnt_ints);
    }
    return cnt;
}

Вот API. Как и любой программный компонент его надо проинициализировать load_detect_init() , затем прокручивать load_detect_proc() где-то с супер цикле.

#ifndef LOAD_DETECT_DRIVER_H
#define LOAD_DETECT_DRIVER_H

#include <stdbool.h>
#include <stdint.h>

#include "load_detect_config.h"
#include "load_detect_types.h"

bool load_detect_init(void);
bool load_detect_proc(void);

#endif /* LOAD_DETECT_DRIVER_H  */

А это код самого драйвера load-detect. Как видите все функции тривиальные и помещаются на один экран.

#include "load_detect_drv.h"

#include <stdint.h>

#include "gpio_drv.h"
#include "log.h"
#include "time_utils.h"

LoadDetectHandle_t* LoadDetectGetNode(uint8_t num) {
    LoadDetectHandle_t *LdNode = NULL;
    uint32_t i = 0;
    uint32_t cnt = load_detect_get_cnt();
    for (i = 0; i < cnt; i++) {
        if (num == LoadDetectInstance[i].num) {
            if (LoadDetectInstance[i].valid) {
                LdNode = &LoadDetectInstance[i];
                break;
            }
        }
    }
    return LdNode;
}

const LoadDetectConfig_t* LoadDetectGetConfNode(uint8_t num) {
    const LoadDetectConfig_t *LDConfig = NULL;
    uint32_t i = 0;
    uint32_t cnt = load_detect_get_cnt();
    for (i = 0; i < cnt; i++) {
        if (num == LoadDetectConfig[i].num) {
            if (LoadDetectConfig[i].valid) {
                LDConfig = &LoadDetectConfig[i];
                break;
            }
        }
    }
    return LDConfig;
}

const LoadDetectPinConfig_t* LoadDetectGetPinConfNode(uint8_t pin_num) {
    const LoadDetectPinConfig_t *PinConfig = NULL;
    uint32_t i = 0;
    uint32_t cnt = load_detect_get_pin_cnt();
    for (i = 0; i < cnt; i++) {
        if (pin_num == LoadDetectPinConfig[i].pin_num) {
            if (LoadDetectPinConfig[i].valid) {
                PinConfig = &LoadDetectPinConfig[i];
                break;
            }
        }
    }
    return PinConfig;
}

LoadDetectPinInfo_t* LoadDetectGetPinNode(uint8_t pin_num) {
    LoadDetectPinInfo_t *PinNode = NULL;
    uint32_t i = 0;
    uint32_t pin_cnt = load_detect_get_pin_cnt();
    for (i = 0; i < pin_cnt; i++) {
        if (pin_num == LoadDetectPinInstance[i].pin_num) {
            if (LoadDetectPinInstance[i].valid) {
                PinNode = &LoadDetectPinInstance[i];
                break;
            }
        }
    }
    return PinNode;
}

static bool load_detect_init_pin(const LoadDetectPinConfig_t* const PinConfig,LoadDetectPinInfo_t* const  PinNode) {
    bool res = false;
    if(PinConfig) {
        if(PinNode) {
            uint32_t ok = 0 ;
            LOG_WARNING(LOAD_DETECT, "InitPad: %s In PullAir", GpioPad2Str(PinConfig->pad.byte));
            PinNode->num = PinConfig->num;
            PinNode->valid = PinConfig->valid;
            PinNode->pad = PinConfig->pad;
            PinNode->pin_num = PinConfig->pin_num;
            PinNode->on_off = true;

            PinNode->state = LOAD_DETECT_OUT_UNDEF;
            PinNode->prev_state = LOAD_DETECT_OUT_UNDEF;
            PinNode->llevel_at_pullair = GPIO_LVL_UNDEF;
            PinNode->llevel_at_pulldown = GPIO_LVL_UNDEF;
            PinNode->llevel_at_pullup = GPIO_LVL_UNDEF;

            res = gpio_set_dir(  PinConfig->pad.byte, GPIO_DIR_IN) ;
            if(res) {
                ok++;
            } else {
                LOG_ERROR(LOAD_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(LOAD_DETECT, "Pad: %s SetPullAir Err", GpioPad2Str(PinConfig->pad.byte));
            }

            if(3==ok){
                res = true;
            }else{
                res = false;
            }
        }
    }

    return res;
}

bool load_detect_init_pins(uint8_t num) {
    bool res = false;
    uint32_t pin_cnt = load_detect_get_pin_cnt();
    LOG_WARNING(LOAD_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 == LoadDetectPinConfig[i].num) {
            res= load_detect_init_pin(&LoadDetectPinConfig[i],&LoadDetectPinInstance[i]);
            if (res) {
                ok++;
                LOG_DEBUG(LOAD_DETECT, "InitPin %s Ok", GpioPad2Str(LoadDetectPinInstance[i].pad.byte));
            } else {
                LOG_ERROR(LOAD_DETECT, "InitPinErr %d", num);
            }
        }
    }
    if(0<ok){
        res = true;
    }
    return res;
}

bool load_detect_init_one(uint8_t num) {
    bool res = false;
    LOG_WARNING(LOAD_DETECT, "Init %d", num);
    const LoadDetectConfig_t *Config = LoadDetectGetConfNode(num);
    if (Config) {
        LoadDetectHandle_t *Node = LoadDetectGetNode(num);
        if (Node) {
            Node->gpio_class = Config->gpio_class;
            Node->init_done = true;
            Node->on_off = true;
            Node->valid = true;
            Node->state =  GPIO__PULL_AIR;
            Node->spin_cnt = 0;
            res = load_detect_init_pins(num);
            if(res){
                LOG_INFO(LOAD_DETECT, "%u InitPinsOk",num);
            }else{
                LOG_ERROR(LOAD_DETECT, "%u InitPinsErr",num);
            }
        } else {
            LOG_ERROR(LOAD_DETECT, "%u NodeErr",num);
        }
    } else {
        LOG_ERROR(LOAD_DETECT, "%u ConfErr",num);
    }
    return res;
}

bool load_detect_init(void) {
    bool res = false;
    log_level_set(LOAD_DETECT, LOG_LEVEL_DEBUG);
    uint32_t cnt = load_detect_get_cnt();
    uint32_t ok = 0;
    LOG_WARNING(LOAD_DETECT, "Init Cnt %d", cnt);

    uint32_t i = 0;
    for (i = 1; i <= cnt; i++) {
        res = load_detect_init_one(i);
        if (res) {
            ok++;
            LOG_INFO(LOAD_DETECT, "LD%u InitOk",i);
        }else{
            LOG_ERROR(LOAD_DETECT, "LD%u InitErr",i);
        }
    }

    if (ok) {
        res = true;
        LOG_INFO(LOAD_DETECT, "Init %u Ok",ok);
    } else {
        res = false;
        LOG_ERROR(LOAD_DETECT, "InitErr");
    }

    log_level_set(LOAD_DETECT, LOG_LEVEL_INFO);
    return res;
}


static bool load_detect_set_mcu_ll(LoadDetectHandle_t *Node, GpioPullMode_t pull_mode) {
    bool res = false;
    uint32_t i = 0;
    uint32_t ok = 0;
    uint32_t cnt = load_detect_get_pin_cnt();
    for (i = 1; i <= cnt; i++) {
        LoadDetectPinInfo_t *PinNode = LoadDetectGetPinNode(i);
        if (PinNode) {
            if (PinNode->num == Node->num) {
                res = gpio_set_pull(PinNode->pad.byte, pull_mode);
                if (res) {
                    ok++;
                }
            }
        }
    }

    res = (ok == cnt) ? true : false;

    return res;
}

static bool load_detect_pin_update(LoadDetectHandle_t *Node,
        LoadDetectPinInfo_t *PinNode, GpioLogicLevel_t logic_level) {
    bool res = false;
    LOG_DEBUG(LOAD_DETECT, "Update: %u %s %s %s" , Node->num,GpioPad2Str(PinNode->pad.byte), GpioPull2Str(Node->state),GpioLevel2Str(logic_level));
    switch (Node->state) {
    case GPIO__PULL_AIR: {
        PinNode->llevel_at_pullair = logic_level;
        res = true;
    }
        break;
    case GPIO__PULL_DOWN: {
        PinNode->llevel_at_pulldown = logic_level;
        res = true;
    }
        break;
    case GPIO__PULL_UP: {
        PinNode->llevel_at_pullup = logic_level;
        res = true;
    }
        break;
    default:
        break;
    }
    return res;
}

static bool load_detect_measure_mcu_ll(LoadDetectHandle_t *Node) {
    bool res = false;
    LOG_DEBUG(LOAD_DETECT, "ProcMeasureMcu:%u", Node->num);
    uint32_t i = 0;
    uint32_t cnt = load_detect_get_pin_cnt();
    for (i = 1; i <= cnt; i++) {
        LoadDetectPinInfo_t *PinNode = LoadDetectGetPinNode(i);
        if (PinNode) {
            if (PinNode->num == Node->num) {
                GpioLogicLevel_t logic_level = GPIO_LVL_UNDEF;
                res = gpio_get_state(PinNode->pad.byte, &logic_level);
                if (res) {
                    res = load_detect_pin_update(Node, PinNode, logic_level);
                }
            }
        }
    }

    return res;
}

static bool load_detect_set_pull_ll(LoadDetectHandle_t *Node, GpioPullMode_t pull_mode) {
    bool res = false;
    switch (Node->gpio_class) {
    case GPIO_CLASS_MCU:
        res = load_detect_set_mcu_ll(Node, pull_mode);
        break;
    case GPIO_CLASS_DW1000:
        res = false;
        break;
    case GPIO_CLASS_DW3000:
        res = false;
        break;
    default:
        LOG_ERROR(LOAD_DETECT, "UndefGPIO");
        break;
    }
    return res;
}

static bool load_detect_measure(LoadDetectHandle_t *Node) {
    bool res = false;
    LOG_DEBUG(LOAD_DETECT, "ProcMeasure:%u", Node->num);
    switch (Node->gpio_class) {
    case GPIO_CLASS_MCU:
        res = load_detect_measure_mcu_ll(Node);
        break;
    case GPIO_CLASS_DW1000:
        res = false;
        break;
    case GPIO_CLASS_DW3000:
        res = false;
        break;
    default:
        LOG_ERROR(LOAD_DETECT, "UndefGPIOclass");
        break;
    }
    return res;
}

static bool load_detect_calc_pin_solution(LoadDetectHandle_t *Node, LoadDetectPinInfo_t* PinNode){
    bool res = false;
    if(Node){
        LOG_DEBUG(LOAD_DETECT, "CalcSolution:%u", Node->num);
        if(PinNode) {
            if(PinNode->num == Node->num){
                switch((uint8_t)PinNode->llevel_at_pullup){
                    case GPIO_LVL_LOW: {
                        PinNode->state = LOAD_DETECT_OUT_SHORT_GND;
                        res = true;
                    }break;
                    case GPIO_LVL_HI: {
                        res = true;

                    }break;
                }

                switch((uint8_t)PinNode->llevel_at_pulldown){
                    case GPIO_LVL_LOW: {
                        res = true;
                    }break;
                    case GPIO_LVL_HI: {
                        PinNode->state = LOAD_DETECT_OUT_SHORT_VCC;
                        res = true;

                    }break;
                }

                if(GPIO_LVL_LOW==PinNode->llevel_at_pulldown) {
                    if(GPIO_LVL_HI==PinNode->llevel_at_pullup){
                        PinNode->state =  LOAD_DETECT_OUT_OPEN;
                        res = true;
                    }
                }

                if(PinNode->prev_state!=PinNode->state){
                    LOG_WARNING(LOAD_DETECT,"Pad %s NewState %s->%s",GpioPad2Str(PinNode->pad.byte),LoadDetectOut2Str(PinNode->prev_state),LoadDetectOut2Str(PinNode->state));
                }
                PinNode->prev_state = PinNode->state;
            }
        }
    }
    return res;
}

static bool load_detect_calc_solution(LoadDetectHandle_t *Node){
    bool res = false;
    uint32_t pin_cnt = load_detect_get_pin_cnt();
    LOG_DEBUG(LOAD_DETECT, "CalcSolution:%u for %u pins", Node->num, pin_cnt);
    Node->spin_cnt++;
    uint32_t i = 0 ;
    uint32_t ok = 0 ;
    for(i=0; i<pin_cnt; i++) {
        res = load_detect_calc_pin_solution(Node,&LoadDetectPinInstance[i]);
        if(res){
            ok++;
        }
    }

    if(pin_cnt==ok){
        res = true;
    }else{
        res = false;
    }
    return res;
}

static bool load_detect_proc_air_ll(LoadDetectHandle_t *const Node) {
    bool res = false;
    LOG_DEBUG(LOAD_DETECT, "ProcAir:%u", Node->num);
    if (ONE_STATE_TIME_OUT_MS < Node->pause_ms) {
        load_detect_measure(Node);
        Node->state = GPIO__PULL_DOWN;
        Node->time_start = time_get_ms();
        LOG_DEBUG(LOAD_DETECT, "SwitchState Air->Down");
        res = load_detect_set_pull_ll(Node, GPIO__PULL_DOWN);
    }

    return res;
}

static bool load_detect_proc_down_ll(LoadDetectHandle_t *Node) {
    bool res = false;
    LOG_DEBUG(LOAD_DETECT, "ProcDown:%u", Node->num);
    if (ONE_STATE_TIME_OUT_MS < Node->pause_ms) {
        load_detect_measure(Node);
        Node->state = GPIO__PULL_UP;
        Node->time_start = time_get_ms();
        LOG_DEBUG(LOAD_DETECT, "SwitchState Down->Up");
        res = load_detect_set_pull_ll(Node, GPIO__PULL_UP);
    }

    return res;
}

static bool load_detect_proc_up_ll(LoadDetectHandle_t *Node) {
    bool res = false;
    LOG_DEBUG(LOAD_DETECT, "ProcUp:%u", Node->num);
    if (ONE_STATE_TIME_OUT_MS < Node->pause_ms) {
        load_detect_measure(Node);
        Node->state = GPIO__PULL_AIR;
        Node->time_start = time_get_ms();
        LOG_DEBUG(LOAD_DETECT, "PullState:Up->Air");
        res = load_detect_set_pull_ll(Node, GPIO__PULL_AIR);

        res=load_detect_calc_solution(Node);
    }

    return res;
}

/*UP->AIR->Down->Up*/
bool load_detect_proc_one(uint8_t num) {
    bool res = false;
    uint32_t up_time = time_get_ms();
    LOG_DEBUG(LOAD_DETECT, "Proc:%u UpTime %u ms", num,up_time);
    LoadDetectHandle_t *Node = LoadDetectGetNode(num);
    if (Node) {
        if (Node->on_off) {
            Node->pause_ms = up_time - Node->time_start;
            LOG_DEBUG(LOAD_DETECT, "Proc Cnt:%u Pause %u ms", num, Node->pause_ms);
            switch (Node->state) {
            case GPIO__PULL_AIR:
                res = load_detect_proc_air_ll(Node);
                break;
            case GPIO__PULL_DOWN:
                res = load_detect_proc_down_ll(Node);
                break;
            case GPIO__PULL_UP:
                res = load_detect_proc_up_ll(Node);
                break;
            default:
                Node->state = GPIO__PULL_AIR;
                res = false;
                break;
            }
        }
    } else {
        LOG_ERROR(LOAD_DETECT, "NodeErr %u",num);
    }
    return res;
}

bool load_detect_proc(void) {
    bool res = false;
    uint8_t ok = 0;
    uint8_t cnt = load_detect_get_cnt();
    LOG_DEBUG(LOAD_DETECT, "Proc Cnt:%u", cnt);
    for (uint32_t i = 1; i <= cnt; i++) {
        res = load_detect_proc_one(i);
        if (res) {
            ok++;
        }
    }

    if (ok) {
        res = true;
    } else {
        res = false;
    }

    return res;
}

Как видите драйвер load-detect разом командует сразу всеми N пинами из конфига, подобно тому как в армии лейтенант командует сразу всем взводом (ровняясь! смирно! Напра-аааа-во !). Поэтому все N пинов переключают свои подтяжки синхронно.

Фаза 7. отладить конечный автомат

Для того чтобы просмотреть отчет работы LD вам в прошивку надо добавить интерфейс командной строки поверх UART-CLI. Что это и зачем есть отдельный текст
https://habr.com/ru/articles/694408/
Иначе без UART-CLI Вы просто никогда не узнаете, где, собственно, обнаружились короткие замыкания. В прошивке отчет LD выглядит как ASCII табличка, где каждому GPIO пину поставлено в соответствие состояние его нагрузки: short GND/VCC или оpen-load.

По-хорошему для верификации микроконтроллерных плат с производства должна быть собрана отдельная прошивка (сборка), которая прокручивает шестерни механизма load-detect. В этой сборке должны быть такие компоненты как TIMER, GPIO, UART, CLI, LD, LED. Эту сборку обычно называют BoardName_IO_Bang. Получается так, что плата как бы изнутри тестирует сама себя.

Лично мне load-detect однажды очень помог найти один чрезвычайно красивый аппаратный баг в первой ревизии одной новой платы. Схемотехники для мультиплексора RS2058 при трассировке взяли для схемотехники распиновку от корпуса MSOP10, а PCB поставили корпус QFN. Пины для MSOP10 и QFN, естественно, не совпадают по номерам. В результате мультиплексор RS2058 не пропускал сигнал и вообще не управлялся.

Вывод

При помощи манипуляции подтяжками напряжений на пинах микроконтроллера и измерений в нужный момент логического уровня на GPIO пине можно запросто определять такие высокоуровневые события как короткое замыкание на GND/VCC или отсутствие нагрузки (подключено бесконечное сопротивление).

Добавляйте в свои прошивки компонент load-detect. Это позволит Вам делать bring-up новых электронных плат легко и эффективно.

Акроним

Расшифровка

GPIO

general-purpose input/output

LD

Load Detect

GND

заземление (Ground)

MCU

MicroController Unit

API

Application programming interface

VCC

Voltage at the Common Collector (Supply voltage )

I2C

Inter-Integrated Circuit

FSM

finite-state machine

PCB

printed circuit board

КЗ

Короткое замыкание

BGA

Ball grid array

ASIC

Application-Specific Integrated Circuit

Links

load detect one pin look up table

H-мост: Load Detect (или как выявлять вандализм)

Почему Нам Нужен UART-Shell? (или Добавьте в Прошивку Гласность)

https://habr.com/ru/articles/681280/

https://en.wikipedia.org/wiki/Mealy_machine

https://habr.com/ru/articles/762142/

Контрольные вопросы:

— Как делать тест PCB на пропай?

-- Что такое конечный автомат Мили?

-- Сколько времени длится переходной процесс установки подтяжки напряжения?

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы делаете load-detect для контроля пайки в новых электронных платах?
2.94%да1
97.06%нет33
Проголосовали 34 пользователя. Воздержались 7 пользователей.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы делаете отдельные тестировочные прошивки для контроля качества изготовления электронных плат?
41.67%да15
58.33%нет21
Проголосовали 36 пользователей. Воздержались 8 пользователей.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы поняли из этого текста механизм работы load-detect?
75.61%да31
24.39%нет10
Проголосовал 41 пользователь. Воздержались 4 пользователя.