Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: LPC1768 uart bootloader
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > ARM, 32bit
Cosmojam
Всем привет!
Помогите победить проблему.
Пробую сделать загрузчик по юарту. Позже планируется usb, но пока с этим ничего не получается.
За основу взят usb bootloader от NXP AN10866 http://www.nxp.com/documents/other/LPC1700..._bootloader.zip
В аттаче архив с 2 проектами для LPCXpresso. UART_bootloader - собственно бутлодырь, точнее только его скелет с самым самым минимумом. test - светодиодная моргалка, которую надо загружать для теста.
Проблема такова:
Загрузчик работает верно, бинарный файл с прошивкой тестового проекта сохраняет как нужно (файл не выравнен по 512байт, то чего не хватает для записи последнего блока дописываю ручками, временно так). Проверки при записи проходят т.е. неверно залитый файл исключается.
При старте он проверяет контрольную сумму прошивки в секторе 4 (туда по умолчанию загружаем). Сумма есть в бинарнике.
Сумма сошлась, надо переходить к исполнению программы с 4 сектора.
Указываем новое местоположение таблицы векторов прерываний, смещаем программный счётчик и верхушку стека (всё как в NXP-шном примере, только там для Keil, а у меня gcc с другим синтаксисом ассемблера):
Код
void boot(uint32_t a)
{
    asm(
    "LDR SP, [R0]\n"
    "LDR PC, [R0, #4]\n"
    );
}

void execute_user_code(void)
{
    uint32_t addr=(uint32_t)sector_start_adress[USERCODE_SECTOR_START];
    SCB->VTOR = (addr & 0x1FFFFF80);
    boot(addr);
}

Успешно попадаем в ResetISR загруженной программы (смотрю пошагово в отладчике и сверяю адреса инструкций с адресами в .map файле)
Дальше самое интересное:
Тестовая программа:
Код
volatile uint32_t del;
void _delay(uint32_t delay)
{
    uint32_t i;
    for(i = 0; i < delay; i++ )
        del = i; // do this so that the compiler does not optimize away the loop.
}


int main(void)
{
//    SystemInit();

//    init_uart(57600);

    LPC_SC->PCONP |= ( 1 << 15 ); // power up GPIO
    LPC_GPIO0->FIODIR |= 1 << 1;
    LPC_GPIO0->FIOPIN = 1<<1;

//    uart_sendstring("YES\n");

    uint32_t i;
    const uint32_t delay=1<<22;
    while(1)
    {
    //    _delay(1<<22);
        for(i = 0; i < delay; i++ )
            del = i;
        LPC_GPIO0->FIOPIN ^= 1 << 1;
    }
    return 0;
}

В таком виде работает, светодиод на P1.1 моргает. Но стоит раскомментировать задержку в вечном цикле и закомментировать "инлайновую" задержку (так она в теле цикла, а станет функцией) - всё, получаем HardFault Precise Error (скачок на адрес 0x40F6 где лежит обработчик HardFault-ов). Тоже самое и с юартом и системинитом. Можно предположить косяк со стеком при вызове функции, но фактически стек работает верно. Например, если убрать системинит, но оставить юарт, то хардфолтов не будет, он исправно пробежит uart_sendstring("YES\n"); но ничего не отправит. Та же программа зашитая в 0 сектор как обычно (с соответсвующей правкой скрипта линкера) работает абсолютно верно.
Оно мне уже мозг вынесло. Подскажите плз, что я делаю не так?
Dron_Gus
А приложение точно собрано для того адреса, по которомц Вы его грузите? Точно-точно? Уж очень симптомы похожи на выполнение ни с того адреса.
Cosmojam
Точно-точно. По крайней мере скрипты линкера верные, в map-файле адреса тоже верные.
Вчера уже в полу-сонном состоянии решил добавить задержку (for на 10000) перед переходом на адрес программы из загрузчика и оно заработало! юарт начал печатать, функции вызываться, диод моргать.Но радость была не долгой sad.gif Попробовал залить реальную программу где уже 70кб набыдлокодено и в ней получаю похожие симптомы: то Imprecise Error прямо в ResetISR, то после срабатывания 2-го прерывания от таймера, то ещё что-то, глючит по-полной в общем. Опять же адрес начала верный MFlash512 (rx) : ORIGIN = 0x4000, LENGTH = 0x7C000 в map файле адреса верные. Эти симптомы с этой программой похожи на те, с которыми я столкнулся пытаясь разместить загрузчик в коде основной программы, но по специальному адресу. Там тоже сначала ни в какую не получалось, потом помогли на форуме lpcxpresso чтобы заработал тестовый проект, а когда те же скрипты линкера перенёс в реальный проект то получил такие глюки. Вот эта тема http://knowledgebase.nxp.com/showthread.php?t=2670 . Но если проблема в моём быдлокоде, то почему он стабильно работает из 0 сектора?! Продолжаю мучиться...
toweroff
стартовый адрес точно __main ?

упс.. прошу прощения sad.gif

у Вас действительно ResetISR точка входа
Cosmojam
Поступило предложение использовать барьеры памяти и сбросить конвейер перед "прыжком в другую реальность". Почитал что это и как, не особо хорошо понял этот механизм, но всё же добавил:
Код
   __asm (
            "dmb\n"
            "dsb\n"
            "isb\n"
        );

Прямо перед вызовом функции, изменяющей PC в загрузчике. А так же в моей программе в ResetISR прямо в самом начале.
Вот что происходит в этом случае:
Загрузчик всё так же успешно отрабатывает, обновляет SP и PC, прыгаем в ResetISR моей программы и на этапе инициализации data/bss происходит сразу 3 пакости: Access Violation, Stack Error, Precise Error. Но что интересно они происходят не без причины. SP внезапно изменяется на 3FE т.е. какой-то код в загрузчике, поскольку в моей программе эта область не отмечена вообще никак - происходит Access Violation. Но кто мог так надругаться над стеком?? Затрейсить выполнение нечем, но в отладчике отображаются красным цветом с градиентом последние 4 команды и это всё тот же цикл в инициализации секторов data/bss, т.е. дальше этого места она никак не могла выпрыгнуть. Есть вариант что кончается память и "куча" дорастает до стека, но этого не может быть т.к. у меня нет столько глобальных переменных, есть парочка статических буферов, один на 4k, другой на 256 и ещё по мелочи, но это никак не переползёт до 32k если только не произойдёт какой-то бяки и цикл инициализации не "взбесится". Но какого лешего тогда оно работает из 0 сектора?
Вот что интересно, если не добавлять барьеры и сброс конвейера в начале ResetISR, то поведение будет другое. SP уже так не изменяется, остаётся на 10007ED8 (начальная верхушка 10008000) во время хардфолта, и ошибка - Bus Error. Тут не удаётся увидеть что было перед этим (видимо отладчик глючит), но судя по такому отрастанию стека там уже какие-то функции успели вызваться. Если же убрать барьеры и из загрузчика, то будет примерно также, но ошибка Imprecise Error и тот же хардфолт.
Кошмар sm.gif
toweroff
прямо несколько минут назад поднимали одну мою ветку
там упоминалось, что __main, в числе прочего, вызывает еще и __user_initial_stackheap, а потом уже main()
может, тут собака порылась? (хотя там применительно к Keil, возможно, в GCC все по-другому)
Cosmojam
Всё намного проще biggrin.gif
Ну почему люди такие идиоты? Это я про себя. Банальнейшая ошибка в загрузчике из-за которой блоки записывались неверно но проверялись верно, настолько банальная, что признаться стыдно. Вот так ищем ошибку там где её нет, и не замечаем на самом видном месте.
А заметил вот как: в LPCXpresso можно прошивать бинарник со смещением на любой адрес. Ради интереса пошил так свою программу, загрузчик там тоже был и вот оно побежало как положено. Спасибо всем за отклики!
Что примечательно до этого 3 суток загара перед монитором и никакого толку, а тут отвлёкся с друзьями в баре по пиву, вернулся домой и почти сразу "попёрло" biggrin.gif Зато поближе с архитектурой проца познакомился.
Остались несколько белых пятен насчёт задержек перед прыжком, переключения тактовых частот и барьеров, но это уже по ходу дела разберусь.
Cosmojam
В догонку ещё вопрос по теме бутлодырей.
Что если мне нужно вызвать загрузчик из основной программы, но при этом передав ему параметр? Т.е. в основной программе видим что с компьютера приходит запрос на соединение от программы для обновления прошивки. На это нужно перепрыгнуть обратно в загрузчик, но при этом знать что сейчас будет обновление прошивки и не нужно проверять суммы и стартовать программу. Обычно это делают с помощью кнопки или небольшой задержки при старте загрузчика чтобы попытки соединения уже шли во время его старта. А можно как-то более изящно? На ум приходит глобальная переменная по жёстко заданному адресу доступному обоим в загрузчике и в программе, но ведь она будет инициализирована в 0 всякий раз при старте загрузчика, а портить стартап код не хочется ради этого. Или в каком-нибудь регистре передать параметр и на самом входе в ResetISR загрузчика забрать этот параметр из регистра? Но не будет ли в регистре произвольный мусор при POR? Подскажите как лучше это сделать?
toweroff
Жестко прописанная переменная в Non-ZI сегменте
Dron_Gus
SVC? Можно сдвинуть таблицу векторов обратно на бутлоадер и вызвать. А можно просто взять адрес из таблицы векторов бутлоадера и вызвать напрямую.
Сергей Борщ
QUOTE (Cosmojam @ Dec 10 2011, 00:05) *
На это нужно перепрыгнуть обратно в загрузчик, но при этом знать что сейчас будет обновление прошивки и не нужно проверять суммы и стартовать программу.
То есть нужен ровно 1 бит информации. Делаете программный ресет и в загрузчике проверяете его флаг. Убиваете сразу двух кроликов: 1) попадаете в загрузчик без лишних теодвижений
2) Сбрасываете всю периферию, в том числе и настроенный приложением WDT, в известное и четко определенное состояние.
QUOTE (Cosmojam @ Dec 10 2011, 00:05) *
Обычно это делают с помощью кнопки или небольшой задержки при старте загрузчика чтобы попытки соединения уже шли во время его старта.
Чушь. Так делают студенты. Кнопка - еще куда ни шло как средство аварийного запуска загрузчика, помогает на этапе разработки после заливки мертвой прошивки. А в общем случае устройство должно перешиваться без участия человека, по команде через штатный интерфейс.
Cosmojam
Цитата(toweroff @ Dec 10 2011, 11:25) *
Жестко прописанная переменная в Non-ZI сегменте

Так ведь глобальные переменные инициируются в 0, т.е. нужно править стартап чтобы эту переменную не потёрли в ResetISR.
Цитата(Dron_Gus @ Dec 10 2011, 12:45) *
SVC? Можно сдвинуть таблицу векторов обратно на бутлоадер и вызвать. А можно просто взять адрес из таблицы векторов бутлоадера и вызвать напрямую.

Да, только бутлодырь должен уметь отличать вызвали его программно, или по POR.
Цитата(Сергей Борщ @ Dec 10 2011, 13:45) *
То есть нужен ровно 1 бит информации. Делаете программный ресет и в загрузчике проверяете его флаг. Убиваете сразу двух кроликов: 1) попадаете в загрузчик без лишних теодвижений
2) Сбрасываете всю периферию, в том числе и настроенный приложением WDT, в известное и четко определенное состояние.
Чушь. Так делают студенты. Кнопка - еще куда ни шло как средство аварийного запуска загрузчика, помогает на этапе разработки после заливки мертвой прошивки. А в общем случае устройство должно перешиваться без участия человека, по команде через штатный интерфейс.

Вы имеете в виду програмный ресет от вочдога и проверку флага WDTR в регистре RSID ?
toweroff
Цитата(Cosmojam @ Dec 10 2011, 21:54) *
Так ведь глобальные переменные инициируются в 0, т.е. нужно править стартап чтобы эту переменную не потёрли в ResetISR.

а Вы читаете, что я пишу? в NON-Zero-Init сегменте
после программного ресета сегмент никто не тронет, проц просто рестартнет
прочитаете магическое слово(байт, бит) - все, меня программно рестартнули, значит надо ждать прошивку
Сергей Борщ
QUOTE (Cosmojam @ Dec 10 2011, 19:54) *
Вы имеете в виду програмный ресет от вочдога и проверку флага WDTR в регистре RSID ?
Нет, я имел ввиду сброс через SYSRESETREQ. К сожалению,
QUOTE
Note: support for
SYSRESETREQ is not included in LPC17xx devices.
Значит, такой вариант отпадает.
toweroff
Цитата(Сергей Борщ @ Dec 11 2011, 04:47) *
Нет, я имел ввиду сброс через SYSRESETREQ. К сожалению, Значит, такой вариант отпадает.

а чем плоха перезагрузка вотчдогом?
Cosmojam
Цитата(toweroff @ Dec 11 2011, 02:27) *
а Вы читаете, что я пишу? в NON-Zero-Init сегменте

Сорри, не понял, сразу. В скрипте линкера оно так выглядит:
Код
    .uninit_RESERVED : ALIGN(4)
    {
        KEEP(*(.bss.$RESERVED*))
    } > RamLoc32

верно?

Кстати с вотчдогом получилось. Спасибо за подсказку
Код
if(LPC_SC->RSID & (1 << 2))
{
    LPC_SC->RSID ^= 1 << 2;
    /* WDT reset */
}
toweroff
Цитата(Cosmojam @ Dec 11 2011, 16:17) *
В скрипте линкера оно так выглядит:
Код
    .uninit_RESERVED : ALIGN(4)
    {
        KEEP(*(.bss.$RESERVED*))
    } > RamLoc32

верно?

тут не подскажу, т.к. пишу в кейле... там все области описывает скаттер-файл
Dron_Gus
Цитата(Cosmojam @ Dec 10 2011, 20:54) *
Да, только бутлодырь должен уметь отличать вызвали его программно, или по POR.

При программном вызове вы попадете не на ресет-вектор. Ни это ли признак?
Cosmojam
Цитата(Dron_Gus @ Dec 12 2011, 23:22) *
При программном вызове вы попадете не на ресет-вектор. Ни это ли признак?

Почему не на ресет вектор? Как раз на него. По идее наверное можно вызвать сразу main() в бутлодыре через указатель на функцию, но я не очень уверен в таком решении. На досуге можно будет поэксперементировать.
Dron_Gus
Я Вам предлагаю вызвать SWI. Для него свой вектор.
Cosmojam
Оно вроде для других целей. Плохо понимаю как можно применить тут, к тому же таблицы векторов для загрузчика и для приложения разные, соответственно вызвать функцию (не важно какую, хоть обработчик прерывания, хоть обычную) - это по сути изменение программного счётчика, т.е. тоже самое что вызвать функцию через указатель. Или я не прав?
Dron_Gus
При вызове SWI процессор переходит в привелигированный режим. К тому же таблица векторов известно где лежит. Зачем плодить сущности?
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.