Можно ли вставить свою собственную процедуру в процедуру сброса?

reset vector

Я работаю с платами ATmega328 NANO и хотел бы узнать, возможно ли ВСТАВИТЬ мою собственную процедуру, чтобы она была включена в то, что делается во время сброса, ДО того, как будет выполнена какая-либо предварительная инициализация C или загрузка нового кода. Если это возможно, я хотел бы знать, как это сделать. Я хотел бы иметь возможность «установить» эту процедуру во время setup(). Я также не ожидаю, что она будет работать после отключения питания. Я говорю о сбросе, вызванном нажатием кнопки сброса, загрузкой кода, открытием последовательного порта или любым сбросом, который НЕ был вызван отключением питания.

Я включаю простой пример наброска, иллюстрирующий то, что я ХОТЕЛ бы сделать, но он, очевидно, не будет соответствовать тому, что я хочу, поскольку я не знаю «волшебного» ответа, который ищу,

Я определил и инициализировал глобальный статический байт вне каких-либо функций. Setup() просто считывает байт из EEPROM и отображает его с помощью Serial.print(), а затем сохраняет его значение в этом глобальном «числе». Теперь обратите внимание, что я создал функцию void(void) с именем magic(). Она просто увеличивает глобальное число и сохраняет его в EEPROM. Я называю это "magic()", потому что неизвестно, что мне нужно сделать, чтобы вызвать это. Мне нужен какой-то способ "установить" magic() во время setup(), чтобы он запускался только как часть процесса сброса, до того, как что-либо в коде или пространстве данных будет затронуто. Под установкой я не подразумеваю просто вызов функции. На самом деле ее не следует вызывать, пока не произойдет сброс. Какую бы "магию" ни пришлось добавить, чтобы это произошло, доказательством этого будет то, что после загрузки скетча каждый раз, когда вы нажимаете RESET или даже снова загружаете скетч, напечатанное число будет другим, потому что оно было увеличено и сохранено при нажатии сброса. ПРИМЕЧАНИЕ: (Я только что отредактировал несколько глупых ошибок здесь)

#include <EEPROM.h>

static uint8_t number =0;

void setup()
{
  uint8_t n = EEPROM.read(0);
  Serial.begin(57600); 
  Serial.print(n);
  number = n;

  // как-то устанавливаем здесь "магическую" процедуру

}

void loop() { }

void magic(void)
{
 ++number;     
 EEPROM.write(0, &number);
}

, 👍0

Обсуждение

почему не в первой строке setup()?, @Juraj


3 ответа


Лучший ответ:

2

Да, это возможно. Смотрите документацию по памяти разделы, из руководства avr-libc. Например:

// Запускаем это после инициализации указателя стека и zero_reg, но
// перед инициализацией ОЗУ.
void __attribute__((naked, used, section(".init3"))) magic(void)
{
    // Что бы ни...
}

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

Также обратите внимание, что если вы просто хотите сохранить некоторые данные с помощью сброс, вы можете поместить его в раздел .noinit, нет необходимости использовать EEPROM.


Правка 1: В ответ на комментарий OP, вот небольшая программа Демонстрация техники. Он просто быстро мигает светодиодом три раза в начале инициализации программы, затем еще три раза, более медленно, во время setup():

#include <util/delay.h>

// Быстро мигните светодиодом три раза в начале инициализации.
void __attribute__((naked, used, section(".init3"))) magic(void)
{
    DDRB |= _BV(PB5);      // контакт PB5 = цифровой 13 как выход
    for (int i= 0; i < 6; i++) {
        PINB |= _BV(PB5);  // переключить PB5
        _delay_ms(100);
    }
}

// Медленно мигните светодиодом три раза.
void setup()
{
    pinMode(13, OUTPUT);
    for (int i = 0; i < 6; i++) {
        delay(300);
        digitalWrite(13, !(i & 1));
    }
}

void loop(){}

Я проверил это на Uno, и это работает.

Обратите внимание, что magic() запускается до того, как будет запущена основная библиотека Arduino. инициализировано. На этом этапе многие основные функции, такие как delay(), не работа. Любая работа, которая здесь выполняется, должна быть выполнена на низком уровне, с использованием только прямой доступ к порту и вызовы avr-libc.

Правка 2: В предыдущей версии этого ответа я забыл использованный атрибут. Он работал со старой настройкой Arduino, которую я тестировал, но не с новейшей IDE. Добавление атрибута used исправляет это.

,

Ну, во-первых... Я немного отредактировал свой скетч из "списка пожеланий", потому что у меня были некоторые глупые ошибки (забыл Serial.begin и неправильно отформатировал вызовы EEPROM). Но, к сожалению, то, что вы предложили, компилируется, но не работает. Если вы попробуете отредактировать мой исправленный скетч с помощью вашего предложения, вы увидите, что он компилируется, но не работает. Доказательством является то, что "255" всегда выводится. Я даже пробовал заменить запись EEPROM на короткий цикл for(): "pinMode(13, OUTPUT); for (int i= 0; i < 10; i++) {digitalWrite(13, i & 1); delay(20);}" Светодиод не мигает при нажатии RESET., @Randy

@Randy: То, что я предложил, работает. Библиотека ядра Arduino не работает, если она не была инициализирована. См. мой отредактированный ответ., @Edgar Bonet

Хм... ну, у меня нет никаких UNO, только платы NANO. Там это не работает. Я понимаю, что разумно начинать с очень низкого уровня и простого примера. Единственное, что я изменил в вашем отредактированном коде, это заставил настройку мигать быстро (задержка 300 мс), а magic() — 1000 мс, чтобы я не пропустил ее. Также пробовал использовать разные уровни инициализации. Компилируется, но в magic() пока нет волшебной пыли. ;-) Я уверен, что это лучший совет, который я получил, и, скорее всего, он сработает. Можете ли вы придумать, почему мне не везет?, @Randy

Почему «PINB |= _BV(PB5)» изменяет состояние вывода светодиода?, @Randy

@Рэнди, потому что в техническом описании так сказано в §18.2.2., @tttapa

@Randy: Моя ошибка. Я не заметил, что текущий набор инструментов Arduino теперь требует атрибут used для работы. Отредактировал ответ соответствующим образом., @Edgar Bonet

Эврика! Но я все еще не уверен. Во-первых, как оказалось, добавление моего EEPROM.write(0, ++number); в magic() работает. Я знаю, потому что если я EEprom.read(0) и выведу его в setup(), он отобразит 1, а не 255. Проблема в том, что при нажатии RESET я надеялся увидеть вывод «2», но вместо этого он остается «1». Если бы magic() действительно запускался ДО инициализации пространства данных, то это было бы так. Я также попробовал удалить «=0», а также удалил ключевое слово static, что должно переместить его в раздел «bss». Но скетч по-прежнему всегда печатает «1». Поэтому magic() запускается до setup(), но не до очистки памяти., @Randy

И я мог бы добавить, причина, по которой я хочу сохранять в EEPROM при сбросе, заключается в том, что я также хочу пережить изменение кода (или улучшение кода в большом проекте). Обычно я сохраняю таким образом при потере питания, с электрической схемой, которая заранее сообщает мне о потере питания AVR, и поскольку это работает, я могу довольно легко просто выключить выключатель питания, затем снова включить его и загрузить свое изменение кода. Но если я также могу сохранять в EEPROM при сбросе, это будет большим бонусом в некоторых проектах., @Randy

@Randy: Я протестировал код в вашем вопросе без каких-либо изменений, кроме добавленных атрибутов. На этот раз я протестировал с версией IDE 1.8.5. Повторное нажатие RESET выводит "207208209210211212...", как и ожидалось. Также обратите внимание, что magic() запускается до инициализации памяти, но _после_ загрузчика, который сам по себе может засорить часть вашей оперативной памяти, тем самым лишив вас цели. Возможно, вам придется написать собственный загрузчик, чтобы обезопасить себя..., @Edgar Bonet

Ну, для меня он печатает одно и то же значение "1" снова и снова, и обновление до 1.8.5 не дало никаких результатов. Я также вижу, что если я изменю его начальное значение 'number' на 1 вместо 0, тот же код будет печатать 129 снова и снова!. Так что я думаю, что это просто не работает с NANO, и в любом случае не является надежным. И действительно, если magic() будет запускаться ПОСЛЕ "bootloader", то документация нам лжет. и это в значительной степени возвращает меня к исходной точке 1. Было бы неплохо, если бы они действительно дали нам настоящий init0, который произошел ДО ВСЕГО, когда вы нажимаете RESET. Думаю, нет., @Randy

Я хотел бы хотя бы поэкспериментировать с разделом "noinit", так как мне где-то сказали, что попытка использовать высокоуровневый EEPROM.write() , как в моем примере, не сработает. Но теперь я не думаю, что мои платы NANO соблюдают НИКАКИЕ правила! Я сделал простой набросок с байтом __attribute__((naked, used, section (".noinit"))) number; Затем моя setup() не делает ничего, кроме Serial.begin(57600); и Serial.println(number++); Он компилируется, но нажатие сброса всегда приводит к печати '0'. Если бы 'number' не инициализировался и питание не отключалось, я бы ожидал, что number (каким бы ни было его значение) будет +1 при каждом запуске. Не повезло. :-(, @Randy

@Randy: 1. «naked» — атрибут для кода, «noinit» — для данных. Смешивать их не имеет смысла. 2. «noinit» означает, что данные не будут перезаписаны кодом запуска среды выполнения C, но это не мешает загрузчику записывать данные в эту часть ОЗУ: загрузчик ничего не знает о вашем скетче., @Edgar Bonet

@EdgarBonet Хорошо... это полезно. Но все же... можете ли вы объяснить это? Простой набросок... начните со строки, в которой говорится "byte number __attribute__((used, section (".noinit"))) ; Затем setup() просто Serial.print() выводит число, а затем увеличивает его. Вот и все. Поэтому, как бы ни начиналось 'number', если я нажму кнопку сброса, набросок должен вывести на единицу больше, чем в прошлый раз. У меня этого просто не происходит... одно и то же число (обычно ноль) выводится снова и снова. Я не могу легко опубликовать целый набросок в комментарии, но я что-то делаю не так? Или "noinit" лжет?, @Randy

@Randy: У меня работает на Uno. Предположительно, загрузчик Nano использует больше оперативной памяти, чем optiboot Uno. В моем тесте number хранился по адресу 0x01c3, после объекта Serial, его vtable, его буферов и переменных хронометража ядра. Вам придется поэкспериментировать, чтобы узнать, какие адреса перезаписывает ваш загрузчик., @Edgar Bonet

@EdgarBone Спасибо... вы подтвердили то, что я подозревал, а именно, что код загрузчика в моем nano не обновлен. Я вижу, как многие люди перепрограммируют NANO с помощью OPTI-BOOT, который предназначен для UNO, но вы можете использовать его с NANO и использовать определение, чтобы убедиться, что все верхние аналоговые входные контакты настроены. Мне нужно либо собрать, либо купить программатор. его легко собрать, но он такой дешевый. Держу пари, что код работает на моем NANO с более современным загрузчиком. Спасибо еще раз., @Randy


1

Вы можете проверить регистр MCUSR, чтобы увидеть причину сброса. В вашем случае вы можете использовать:

if( bit_is_set(MCUSR, EXTRF) )// была нажата кнопка сброса
{
    MCUSR = 0;// очищаем флаг сброса

    // "волшебная" процедура здесь

}
,

Спасибо. Я понимаю это, но мне все равно, что вызвало сброс., @Randy

Ты шутишь? Ты конкретно сказал сброс и «НЕ потерей питания». Ну, теперь мне все равно, @Gerben

Думаю, очевидно, что я имел в виду: я хотел, чтобы он РАБОТАЛ при сбросе настроек, но не ожидаю, что он будет работать при потере питания., @Randy

Было **настолько** очевидно, что два человека дали вам ответ на неправильную интерпретацию этой части вопроса!, @Gerben


0

Если вы используете последнюю версию (7.0, июль 2018 г.) Optiboot, вы можете использовать регистр MCUSR для проверки причины сброса.

Вам необходимо использовать последнюю версию, поскольку предыдущие версии загрузчика очищали MCUSR перед запуском скетча (как рекомендовано в §15.9.1 спецификации ATmega328P). Это было изменено в версии 7.0 (a50f47b).

#include <EEPROM.h>

static uint8_t ch = MCUSR;

void setup() {
  MCUSR = 0;
  uint8_t n = EEPROM.read(0);
  Serial.begin(115200);
  Serial.print(n);
  if ((_BV(PORF) & ch) == 0) // не сброс при включении питания
    magic(n);
}

void loop() { }

void magic(uint8_t &number) {
   EEPROM.put(0, ++number);
}
,

Это интересно, но я уже видел это раньше, и мне все равно, что вызвало сброс. Я хочу (и надеюсь, я объяснил), чтобы процедура, которую я вызвал magic(), запускалась при сбросе, до загрузки нового кода скетча или до любой предварительной очистки C или инициализации памяти/переменных., @Randy

@Randy Вы конкретно спросили: «_Я говорю о сбросе, вызванном нажатием кнопки сброса, загрузкой кода, открытием последовательного порта или любым сбросом, который НЕ был вызван отключением питания_». По какой причине вам нужно, чтобы это произошло до инициализации C?, @tttapa

Я отвечу, но не хочу слишком углубляться в подробности. В сложной системе я обычно помещаю все важное в структуру (состояния, таймеры и т. д.) и сохраняю все это в EEprom перед выключением. Первое, что делает код при запуске, — это считывает структуру обратно из EEprom, чтобы он мог возобновиться в том же самом состоянии. Было бы неплохо, если бы загрузка изменения кода могла сначала запустить мою процедуру сохранения. Я могу найти способ сначала выполнить сохранение вручную, но было бы неплохо автоматизировать это. К сожалению, ничего из того, что я пробовал, не сработало. Даже разделы .init., @Randy

@Randy Для меня это не имеет никакого смысла: когда начинается загрузка, MCU сбрасывается, поэтому ваша конфигурация (состояния, таймеры) сбрасываются и теряются., @tttapa

Опять же, конечной целью является сохранение данных в EEPROM. Я могу вас заверить, что сохранение структуры, содержащей все ваши состояния и таймеры в EEPROM, обеспечивает надежное и прочное восстановление именно там, где ваша программа «остановилась», если первое, что вы сделаете, это восстановите свою структуру из EEPROM, куда вы сохранили., @Randy

@Randy Да, конечно, но это не имеет значения, когда вы считываете конфигурацию EEPROM, вы можете сделать это в настройке. Но если вы хотите сохранить текущее, несохраненное состояние перед обновлением, вы не сможете сделать это после сброса., @tttapa

судя по всему, нет, но если бы существовал способ украсть вектор RESET, как я надеялся, то определенно можно было бы сохранить данные в EEPROM при RESET, если только это не был сброс при включении питания., @Randy