Что может мешать выполнению кода в бутлоадере, если он же работает в основной программе? код с использованием SPI

Всем здрасте!

не покидает меня желание написать загрузчик для заливки прошивки в атмегу по CAN шине.

имеем атмега 328р + SPI CAN контроллер mcp2515 с библиотекой mcp_can.h

для начала в бутлоадере пробую простую логику освоить, чтобы раз в секунду сообщение в CAN выплевывал. Но даже на этом естесственно затык.

написал код в ардуино иде как int main(). убрал из библиотеки стандартные ардуиновские примочки delay и millis и всякие digitalWrite, добился, чтобы код в основной программе нормально заработал.

собрал загрузчик с таким же кодом и тем же файлом библиотеки. через make командую строку . Размер бутлоадера сделал максимальный пока 4кб, фьюзы прошил под это дело тоже. (фьюзы FF F8 FD) . Залил программатором

но сообщения в CAN не идут. поставил через юарт метки , чтобы посмотреть куда заходит программа и вообще не ребутиться ли мк. Но нет, все норм. доходит до лупа и плюется метками в юарт раз в секунду. а в CAN шиш вам.

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

также думал может что-то с адресами векторов прерываний , вычитал что для загрузчика их надо настраивать на начало адреса загрузчика, но судя по bin файлу прошивки с нулевого адреса везде FF. (и загрузчик лежит там где надо с 0х7000) да и пробовал на всякий случай устанавливать биты IVCE и потом IVSEL - не помогло. скорее всего GCC вектора прерываний делает все как надо уже. Но по итогу в основной программе, где код работает, в самом начале программы отключаю прерывания совсем cli() и CAN при этом работает! видимо прерывания, таймеры вообще для этого не используются.

пока собирал файл прошивки с помощью make, файлы библиотеки CAN и остальные - замучался , компилятору все никак не нравилось . то одного не видит, то другого. Опыта в таких делах у меня маловато мягко говоря. пока за размер борьбы нет, в makefile поставил компилятор g++, чтобы классы библиотек компилировались . По итогу все необходимое для компиляции в один mcp_can.h запихал, чтобы не мучиться с инклюдами всеми и в секции основной программы это заработало, в CAN сообщения идут.

но в секции бутлоадера что-то это не работает. Подскажите, в какую сторону копнуть?

Мне кажется что-то с шиной SPI надо настроить. Есть ли какие то моменты в SPI.h которые могут не работать в бутлоадере?

#include "wiring_private.h"
#include <util/delay.h>
#include "mcp_can.h"


MCP_CAN CAN(10); 

byte data[] = {1,2,3,4,5,6,7,8};


int main(void) __attribute__ ((OS_main)) __attribute__ ((section (".init9")));
void putch(char);

int main(void)
{
  
sei(); // включаем прерывания 
//cli();   // выключаем все прерывания 

//ниже настройка юарт0 для меток дебага
UCSR0A = _BV(U2X0); //Double speed mode USART0
UCSR0B = _BV(RXEN0) | _BV(TXEN0);
UCSR0C = _BV(UCSZ00) | _BV(UCSZ01);
UBRR0L = (uint8_t)( (F_CPU + 115200L  * 4L) / (115200L  * 8L) - 1 );

putch(0x31); //1
CAN.begin(MCP_ANY, CAN_250KBPS, MCP_8MHZ);
putch(0x32); //2
CAN.setMode(MCP_NORMAL);

while(1) // loop
   {
CAN.sendMsgBuf(0x555, 0, 1, data); // отправляем сообщение в кан 
byte incr = 0 ; 
while (++incr<200) {_delay_ms(5);} // delay 1 сек
 putch(0x51); //q
   }
return 0;
}


void putch(char ch) {
  while (!(UCSR0A & _BV(UDRE0)));
  UDR0 = ch;
}

SPI.h

Спойлер
/*
 * Copyright (c) 2010 by Cristian Maglie <c.maglie@arduino.cc>
 * Copyright (c) 2014 by Paul Stoffregen <paul@pjrc.com> (Transaction API)
 * Copyright (c) 2014 by Matthijs Kooijman <matthijs@stdin.nl> (SPISettings AVR)
 * Copyright (c) 2014 by Andrew J. Kroll <xxxajk@gmail.com> (atomicity fixes)
 * SPI Master library for arduino.
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of either the GNU General Public License version 2
 * or the GNU Lesser General Public License version 2.1, both as
 * published by the Free Software Foundation.
 */

#ifndef _SPI_H_INCLUDED
#define _SPI_H_INCLUDED

#include <Arduino.h>

// SPI_HAS_TRANSACTION means SPI has beginTransaction(), endTransaction(),
// usingInterrupt(), and SPISetting(clock, bitOrder, dataMode)
#define SPI_HAS_TRANSACTION 1

// SPI_HAS_NOTUSINGINTERRUPT means that SPI has notUsingInterrupt() method
#define SPI_HAS_NOTUSINGINTERRUPT 1

// SPI_ATOMIC_VERSION means that SPI has atomicity fixes and what version.
// This way when there is a bug fix you can check this define to alert users
// of your code if it uses better version of this library.
// This also implies everything that SPI_HAS_TRANSACTION as documented above is
// available too.
#define SPI_ATOMIC_VERSION 1

// Uncomment this line to add detection of mismatched begin/end transactions.
// A mismatch occurs if other libraries fail to use SPI.endTransaction() for
// each SPI.beginTransaction().  Connect an LED to this pin.  The LED will turn
// on if any mismatch is ever detected.
//#define SPI_TRANSACTION_MISMATCH_LED 5

#ifndef LSBFIRST
#define LSBFIRST 0
#endif
#ifndef MSBFIRST
#define MSBFIRST 1
#endif

#define SPI_CLOCK_DIV4 0x00
#define SPI_CLOCK_DIV16 0x01
#define SPI_CLOCK_DIV64 0x02
#define SPI_CLOCK_DIV128 0x03
#define SPI_CLOCK_DIV2 0x04
#define SPI_CLOCK_DIV8 0x05
#define SPI_CLOCK_DIV32 0x06

#define SPI_MODE0 0x00
#define SPI_MODE1 0x04
#define SPI_MODE2 0x08
#define SPI_MODE3 0x0C

#define SPI_MODE_MASK 0x0C  // CPOL = bit 3, CPHA = bit 2 on SPCR
#define SPI_CLOCK_MASK 0x03  // SPR1 = bit 1, SPR0 = bit 0 on SPCR
#define SPI_2XCLOCK_MASK 0x01  // SPI2X = bit 0 on SPSR

// define SPI_AVR_EIMSK for AVR boards with external interrupt pins
#if defined(EIMSK)
  #define SPI_AVR_EIMSK  EIMSK
#elif defined(GICR)
  #define SPI_AVR_EIMSK  GICR
#elif defined(GIMSK)
  #define SPI_AVR_EIMSK  GIMSK
#endif

class SPISettings {
public:
  SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) {
    if (__builtin_constant_p(clock)) {
      init_AlwaysInline(clock, bitOrder, dataMode);
    } else {
      init_MightInline(clock, bitOrder, dataMode);
    }
  }
  SPISettings() {
    init_AlwaysInline(4000000, MSBFIRST, SPI_MODE0);
  }
private:
  void init_MightInline(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) {
    init_AlwaysInline(clock, bitOrder, dataMode);
  }
  void init_AlwaysInline(uint32_t clock, uint8_t bitOrder, uint8_t dataMode)
    __attribute__((__always_inline__)) {
    // Clock settings are defined as follows. Note that this shows SPI2X
    // inverted, so the bits form increasing numbers. Also note that
    // fosc/64 appears twice
    // SPR1 SPR0 ~SPI2X Freq
    //   0    0     0   fosc/2
    //   0    0     1   fosc/4
    //   0    1     0   fosc/8
    //   0    1     1   fosc/16
    //   1    0     0   fosc/32
    //   1    0     1   fosc/64
    //   1    1     0   fosc/64
    //   1    1     1   fosc/128

    // We find the fastest clock that is less than or equal to the
    // given clock rate. The clock divider that results in clock_setting
    // is 2 ^^ (clock_div + 1). If nothing is slow enough, we'll use the
    // slowest (128 == 2 ^^ 7, so clock_div = 6).
    uint8_t clockDiv;

    // When the clock is known at compiletime, use this if-then-else
    // cascade, which the compiler knows how to completely optimize
    // away. When clock is not known, use a loop instead, which generates
    // shorter code.
    if (__builtin_constant_p(clock)) {
      if (clock >= F_CPU / 2) {
        clockDiv = 0;
      } else if (clock >= F_CPU / 4) {
        clockDiv = 1;
      } else if (clock >= F_CPU / 8) {
        clockDiv = 2;
      } else if (clock >= F_CPU / 16) {
        clockDiv = 3;
      } else if (clock >= F_CPU / 32) {
        clockDiv = 4;
      } else if (clock >= F_CPU / 64) {
        clockDiv = 5;
      } else {
        clockDiv = 6;
      }
    } else {
      uint32_t clockSetting = F_CPU / 2;
      clockDiv = 0;
      while (clockDiv < 6 && clock < clockSetting) {
        clockSetting /= 2;
        clockDiv++;
      }
    }

    // Compensate for the duplicate fosc/64
    if (clockDiv == 6)
    clockDiv = 7;

    // Invert the SPI2X bit
    clockDiv ^= 0x1;

    // Pack into the SPISettings class
    spcr = _BV(SPE) | _BV(MSTR) | ((bitOrder == LSBFIRST) ? _BV(DORD) : 0) |
      (dataMode & SPI_MODE_MASK) | ((clockDiv >> 1) & SPI_CLOCK_MASK);
    spsr = clockDiv & SPI_2XCLOCK_MASK;
  }
  uint8_t spcr;
  uint8_t spsr;
  friend class SPIClass;
};


class SPIClass {
public:
  // Initialize the SPI library
  static void begin();

  // If SPI is used from within an interrupt, this function registers
  // that interrupt with the SPI library, so beginTransaction() can
  // prevent conflicts.  The input interruptNumber is the number used
  // with attachInterrupt.  If SPI is used from a different interrupt
  // (eg, a timer), interruptNumber should be 255.
  static void usingInterrupt(uint8_t interruptNumber);
  // And this does the opposite.
  static void notUsingInterrupt(uint8_t interruptNumber);
  // Note: the usingInterrupt and notUsingInterrupt functions should
  // not to be called from ISR context or inside a transaction.
  // For details see:
  // https://github.com/arduino/Arduino/pull/2381
  // https://github.com/arduino/Arduino/pull/2449

  // Before using SPI.transfer() or asserting chip select pins,
  // this function is used to gain exclusive access to the SPI bus
  // and configure the correct settings.
  inline static void beginTransaction(SPISettings settings) {
    if (interruptMode > 0) {
      uint8_t sreg = SREG;
      noInterrupts();

      #ifdef SPI_AVR_EIMSK
      if (interruptMode == 1) {
        interruptSave = SPI_AVR_EIMSK;
        SPI_AVR_EIMSK &= ~interruptMask;
        SREG = sreg;
      } else
      #endif
      {
        interruptSave = sreg;
      }
    }

    #ifdef SPI_TRANSACTION_MISMATCH_LED
    if (inTransactionFlag) {
      pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT);
      digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH);
    }
    inTransactionFlag = 1;
    #endif

    SPCR = settings.spcr;
    SPSR = settings.spsr;
  }

  // Write to the SPI bus (MOSI pin) and also receive (MISO pin)
  inline static uint8_t transfer(uint8_t data) {
    SPDR = data;
    /*
     * The following NOP introduces a small delay that can prevent the wait
     * loop form iterating when running at the maximum speed. This gives
     * about 10% more speed, even if it seems counter-intuitive. At lower
     * speeds it is unnoticed.
     */
    asm volatile("nop");
    while (!(SPSR & _BV(SPIF))) ; // wait
    return SPDR;
  }
  inline static uint16_t transfer16(uint16_t data) {
    union { uint16_t val; struct { uint8_t lsb; uint8_t msb; }; } in, out;
    in.val = data;
    if (!(SPCR & _BV(DORD))) {
      SPDR = in.msb;
      asm volatile("nop"); // See transfer(uint8_t) function
      while (!(SPSR & _BV(SPIF))) ;
      out.msb = SPDR;
      SPDR = in.lsb;
      asm volatile("nop");
      while (!(SPSR & _BV(SPIF))) ;
      out.lsb = SPDR;
    } else {
      SPDR = in.lsb;
      asm volatile("nop");
      while (!(SPSR & _BV(SPIF))) ;
      out.lsb = SPDR;
      SPDR = in.msb;
      asm volatile("nop");
      while (!(SPSR & _BV(SPIF))) ;
      out.msb = SPDR;
    }
    return out.val;
  }
  inline static void transfer(void *buf, size_t count) {
    if (count == 0) return;
    uint8_t *p = (uint8_t *)buf;
    SPDR = *p;
    while (--count > 0) {
      uint8_t out = *(p + 1);
      while (!(SPSR & _BV(SPIF))) ;
      uint8_t in = SPDR;
      SPDR = out;
      *p++ = in;
    }
    while (!(SPSR & _BV(SPIF))) ;
    *p = SPDR;
  }
  // After performing a group of transfers and releasing the chip select
  // signal, this function allows others to access the SPI bus
  inline static void endTransaction(void) {
    #ifdef SPI_TRANSACTION_MISMATCH_LED
    if (!inTransactionFlag) {
      pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT);
      digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH);
    }
    inTransactionFlag = 0;
    #endif

    if (interruptMode > 0) {
      #ifdef SPI_AVR_EIMSK
      uint8_t sreg = SREG;
      #endif
      noInterrupts();
      #ifdef SPI_AVR_EIMSK
      if (interruptMode == 1) {
        SPI_AVR_EIMSK = interruptSave;
        SREG = sreg;
      } else
      #endif
      {
        SREG = interruptSave;
      }
    }
  }

  // Disable the SPI bus
  static void end();

  // This function is deprecated.  New applications should use
  // beginTransaction() to configure SPI settings.
  inline static void setBitOrder(uint8_t bitOrder) {
    if (bitOrder == LSBFIRST) SPCR |= _BV(DORD);
    else SPCR &= ~(_BV(DORD));
  }
  // This function is deprecated.  New applications should use
  // beginTransaction() to configure SPI settings.
  inline static void setDataMode(uint8_t dataMode) {
    SPCR = (SPCR & ~SPI_MODE_MASK) | dataMode;
  }
  // This function is deprecated.  New applications should use
  // beginTransaction() to configure SPI settings.
  inline static void setClockDivider(uint8_t clockDiv) {
    SPCR = (SPCR & ~SPI_CLOCK_MASK) | (clockDiv & SPI_CLOCK_MASK);
    SPSR = (SPSR & ~SPI_2XCLOCK_MASK) | ((clockDiv >> 2) & SPI_2XCLOCK_MASK);
  }
  // These undocumented functions should not be used.  SPI.transfer()
  // polls the hardware flag which is automatically cleared as the
  // AVR responds to SPI's interrupt
  inline static void attachInterrupt() { SPCR |= _BV(SPIE); }
  inline static void detachInterrupt() { SPCR &= ~_BV(SPIE); }

private:
  static uint8_t initialized;
  static uint8_t interruptMode; // 0=none, 1=mask, 2=global
  static uint8_t interruptMask; // which interrupts to mask
  static uint8_t interruptSave; // temp storage, to restore state
  #ifdef SPI_TRANSACTION_MISMATCH_LED
  static uint8_t inTransactionFlag;
  #endif
};

extern SPIClass SPI;

#endif

фъюз SPIEN включить пробовали?

Этот Фьюз естественно включен , иначе , я бы не смог заливать прошивку по spi программатором.
К тому же , на сколько я понимаю , этот Фьюз нельзя выключить программатором spi

Вообще-то загрузчик работает около секунды, следовательно, “раз в секунду” == “один раз”.

Метки в сериал порт это опровергают . При дебаге по юарт метка старта только одна прилетает. Далее он сыплет метками из луп (while (1))

Исходил из

5-й бит при любом варианте ==1

Вот так фьюзы выставлены. Сорян, опечатался там в 1м сообщении, исправлю. Блин уже не даёт исправить

Да, скорее и не в этом дело.

Возможно дело в библиотеках, в том что они “заточены” на инит с 0х00 адреса. Попробуйте заменить аппаратный SPI простым “ногодрыгом”.
Посмотрите осциллографом, что на шине…

Как будет время, сниму лог анализатором что на пинах spi на работающем примере и на неработающем.

Прерывание от SPI задествовано ? И как оно адресуется ?

ИМХО надо уйти от библиотеки SPI как минимум и прерываний - разруливать через регистры настройку и готовность данных в шине SPI …

снял логи в буте и основной программе
скетч такой

#include "wiring_private.h"
#include <util/delay.h>
#include "mcp_can.h"

MCP_CAN CAN(10); 

byte data[] = {1,2,3,4,5,6,7,8};


int main(void) __attribute__ ((OS_main)) __attribute__ ((section (".init9")));
void putch(char);


int main(void)
{
  
sei();

UCSR0A = _BV(U2X0); //Double speed mode USART0
UCSR0B = _BV(RXEN0) | _BV(TXEN0);
UCSR0C = _BV(UCSZ00) | _BV(UCSZ01);
UBRR0L = (uint8_t)( (F_CPU + 115200L  * 4L) / (115200L  * 8L) - 1 );

putch(0x31); //1
CAN.begin(MCP_ANY, CAN_250KBPS, MCP_8MHZ);
putch(0x32); //2
CAN.setMode(MCP_NORMAL);
putch(0x33); //3

while(1) 
   {

putch(0x49);
CAN.sendMsgBuf(0x444, 0, 1, data);
putch(0x50); //q
byte incr = 0 ; 
while (++incr<200) {_delay_ms(5);}
 putch(0x51); //q
   }
return 0;
}

void putch(char ch) 
{
  while (!(UCSR0A & _BV(UDRE0)));
  UDR0 = ch;
} 

странное творится , в бутлоадере SPI шина тоже работает, тактирование идет, обмен данными MISO MOSI идет . Но затык после метки по юарт 0х31. В неработающем примере там почти сразу метка 0х32 появляется. Инит 2515 значит не удался, поэтому она далее не отвечает

а вот лог из программы, где работает (НЕ в бутлоабере). Как видим инит 2515 длится гораздо дольше

а вот ниже место, где уже луп, то есть периодически функция отправки сообщения CAN,
не работающий пример:

а вот ниже работающий пример в секции application

видим что раз в секунду обмен между MCP и атмегой длится очень короткое время, при этом МСР отвечает норм

вот крупнее:

1 лайк

буду смотреть, что там в функции инит не так может быть

файл mcp_can.h

логи с анализатора Logic2

В лог анализаторе есть так же анализ шины spi попробовал - работает! Рисует какие байты летают. Чуть позже время проявится , буду анализировать что там не так.

1 лайк

Не много ли кода, как для бутлоадера?

А есть уверенность, что код полностью “влазит” в память?

Вопрос снят. Не поленился,. проверил. Код менее 1.5 КВ. Места достаточно.

изучил логи, похоже проблема в приеме байт из SPI и видимо все таки получается в прерываниях, изучаю этот момент .

в библиотеке MCP в функции инита , устанавливается режим работы MCP, а затем считывается регистр , как обратная связь типа успешно ли выполнена команда, и дальше в коде, если ответ от MCP отрицательный о выпонении этой команды, то далее if не выполняется и очень много чего пропускается . Дак вот судя по логу ответ от МСР то успешный, но, видимо, АТМЕГА его не считывает, так как прием байт по SPI не работает.

вот что я и вижу в логе где нормально работает МСР:

Вообще, безотносительно архитектуры - если у вас код работает в приложении и не работает в бутлоадере - это всегда недоинициализированное железо.

Прочитайте интересующие вас регистры периферии (SPI например), когда все работает как надо. Сравните с тем, что конфигурирует ваш бутлоадер.

Сравните прерывания. В бутлоадере и в работающем софте - какие разрешены, какие запрещены. У вас, похоже, прерывание на прием не генерируется. Где-то битик забыли поставить , какой-нибудь INT_ENABLE

К сожалению бутлоадеры и прочие startup коды - это то место, где отлаживаться printfом и светодиодиком уже сложно. Нужон нормальный отладчик.

это изначально понятно , только “недоинициализированное железо” слишком обширное поняние . Вот сужаю круг поиска потихоньку. Отлаживать не удобно , очень. это все и затрудняет. Лог анализатор помогает сильно в этом деле . Вопрос как работает шина SPI и MCP поизучал, как будет время, буду дальше тестить, писать какие то маленькие тестовые скетчи. Нужно убедиться , что проблема именно в приеме байт по SPI и тогда подозрение падает на метод SPI.transfer . Бит SPIF возможно не сбрасывается из-за отключенного прерывания SPIE. из-за чего цикл while метода пропускается и регистр SPI SPDR раньше считывается и считываемый байт становится ошибочным. Хотя в этом случае ИМХО и отправка не должна работать , а она работает.

inline static uint8_t transfer(uint8_t data) {
    SPDR = data;
    asm volatile("nop");
    while (!(SPSR & _BV(SPIF))) ; // wait
    return SPDR;
  }