aboutsummaryrefslogtreecommitdiff
path: root/Marlin/src/HAL/STM32/eeprom_flash.cpp
blob: 633a286dc8dd6a0a83b522dba56757112f2fc63b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
/**
 * Marlin 3D Printer Firmware
 *
 * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
 * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com
 * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com
 * Copyright (c) 2016 Victor Perez victor_pv@hotmail.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */
#if defined(ARDUINO_ARCH_STM32) && !defined(STM32GENERIC)

#include "../../inc/MarlinConfig.h"

#if ENABLED(FLASH_EEPROM_EMULATION)

#include "../shared/eeprom_api.h"

/**
 * The STM32 HAL supports chips that deal with "pages" and some with "sectors" and some that
 * even have multiple "banks" of flash.
 *
 * This code is a bit of a mashup of
 *   framework-arduinoststm32/cores/arduino/stm32/stm32_eeprom.c
 *   hal/hal_lpc1768/persistent_store_flash.cpp
 *
 * This has only be written against those that use a single "sector" design.
 *
 * Those that deal with "pages" could be made to work. Looking at the STM32F07 for example, there are
 * 128 "pages", each 2kB in size. If we continued with our EEPROM being 4Kb, we'd always need to operate
 * on 2 of these pages. Each write, we'd use 2 different pages from a pool of pages until we are done.
 */

#if ENABLED(FLASH_EEPROM_LEVELING)

  #include "stm32_def.h"

  #define DEBUG_OUT ENABLED(EEPROM_CHITCHAT)
  #include "src/core/debug_out.h"

  #ifndef MARLIN_EEPROM_SIZE
    #define MARLIN_EEPROM_SIZE    0x1000 // 4KB
  #endif

  #ifndef FLASH_SECTOR
    #define FLASH_SECTOR          (FLASH_SECTOR_TOTAL - 1)
  #endif
  #ifndef FLASH_UNIT_SIZE
    #define FLASH_UNIT_SIZE       0x20000 // 128kB
  #endif

  #ifndef FLASH_ADDRESS_START
    #define FLASH_ADDRESS_START   (FLASH_END - ((FLASH_SECTOR_TOTAL - (FLASH_SECTOR)) * (FLASH_UNIT_SIZE)) + 1)
  #endif
  #define FLASH_ADDRESS_END       (FLASH_ADDRESS_START + FLASH_UNIT_SIZE  - 1)

  #define EEPROM_SLOTS            ((FLASH_UNIT_SIZE) / (MARLIN_EEPROM_SIZE))
  #define SLOT_ADDRESS(slot)      (FLASH_ADDRESS_START + (slot * (MARLIN_EEPROM_SIZE)))

  #define UNLOCK_FLASH()          if (!flash_unlocked) { \
                                    HAL_FLASH_Unlock(); \
                                    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | \
                                                           FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR); \
                                    flash_unlocked = true; \
                                  }
  #define LOCK_FLASH()            if (flash_unlocked) { HAL_FLASH_Lock(); flash_unlocked = false; }

  #define EMPTY_UINT32            ((uint32_t)-1)
  #define EMPTY_UINT8             ((uint8_t)-1)

  static uint8_t ram_eeprom[MARLIN_EEPROM_SIZE] __attribute__((aligned(4))) = {0};
  static int current_slot = -1;

  static_assert(0 == MARLIN_EEPROM_SIZE % 4, "MARLIN_EEPROM_SIZE must be a multiple of 4"); // Ensure copying as uint32_t is safe
  static_assert(0 == FLASH_UNIT_SIZE % MARLIN_EEPROM_SIZE, "MARLIN_EEPROM_SIZE must divide evenly into your FLASH_UNIT_SIZE");
  static_assert(FLASH_UNIT_SIZE >= MARLIN_EEPROM_SIZE, "FLASH_UNIT_SIZE must be greater than or equal to your MARLIN_EEPROM_SIZE");
  static_assert(IS_FLASH_SECTOR(FLASH_SECTOR), "FLASH_SECTOR is invalid");
  static_assert(IS_POWER_OF_2(FLASH_UNIT_SIZE), "FLASH_UNIT_SIZE should be a power of 2, please check your chip's spec sheet");

#endif

static bool eeprom_data_written = false;

#ifndef MARLIN_EEPROM_SIZE
  #define MARLIN_EEPROM_SIZE size_t(E2END + 1)
#endif
size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE; }

bool PersistentStore::access_start() {

  #if ENABLED(FLASH_EEPROM_LEVELING)

    if (current_slot == -1 || eeprom_data_written) {
      // This must be the first time since power on that we have accessed the storage, or someone
      // loaded and called write_data and never called access_finish.
      // Lets go looking for the slot that holds our configuration.
      if (eeprom_data_written) DEBUG_ECHOLNPGM("Dangling EEPROM write_data");
      uint32_t address = FLASH_ADDRESS_START;
      while (address <= FLASH_ADDRESS_END) {
        uint32_t address_value = (*(__IO uint32_t*)address);
        if (address_value != EMPTY_UINT32) {
          current_slot = (address - (FLASH_ADDRESS_START)) / (MARLIN_EEPROM_SIZE);
          break;
        }
        address += sizeof(uint32_t);
      }
      if (current_slot == -1) {
        // We didn't find anything, so we'll just intialize to empty
        for (int i = 0; i < MARLIN_EEPROM_SIZE; i++) ram_eeprom[i] = EMPTY_UINT8;
        current_slot = EEPROM_SLOTS;
      }
      else {
        // load current settings
        uint8_t *eeprom_data = (uint8_t *)SLOT_ADDRESS(current_slot);
        for (int i = 0; i < MARLIN_EEPROM_SIZE; i++) ram_eeprom[i] = eeprom_data[i];
        DEBUG_ECHOLNPAIR("EEPROM loaded from slot ", current_slot, ".");
      }
      eeprom_data_written = false;
    }

  #else
    eeprom_buffer_fill();
  #endif

  return true;
}

bool PersistentStore::access_finish() {

  if (eeprom_data_written) {
    #ifdef STM32F4xx
      // MCU may come up with flash error bits which prevent some flash operations.
      // Clear flags prior to flash operations to prevent errors.
      __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);
    #endif

    #if ENABLED(FLASH_EEPROM_LEVELING)

      HAL_StatusTypeDef status = HAL_ERROR;
      bool flash_unlocked = false;

      if (--current_slot < 0) {
        // all slots have been used, erase everything and start again

        FLASH_EraseInitTypeDef EraseInitStruct;
        uint32_t SectorError = 0;

        EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;
        EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3;
        EraseInitStruct.Sector = FLASH_SECTOR;
        EraseInitStruct.NbSectors = 1;

        current_slot = EEPROM_SLOTS - 1;
        UNLOCK_FLASH();

        TERN_(HAS_PAUSE_SERVO_OUTPUT, PAUSE_SERVO_OUTPUT());
        DISABLE_ISRS();
        status = HAL_FLASHEx_Erase(&EraseInitStruct, &SectorError);
        ENABLE_ISRS();
        TERN_(HAS_PAUSE_SERVO_OUTPUT, RESUME_SERVO_OUTPUT());
        if (status != HAL_OK) {
          DEBUG_ECHOLNPAIR("HAL_FLASHEx_Erase=", status);
          DEBUG_ECHOLNPAIR("GetError=", HAL_FLASH_GetError());
          DEBUG_ECHOLNPAIR("SectorError=", SectorError);
          LOCK_FLASH();
          return false;
        }
      }

      UNLOCK_FLASH();

      uint32_t offset = 0;
      uint32_t address = SLOT_ADDRESS(current_slot);
      uint32_t address_end = address + MARLIN_EEPROM_SIZE;
      uint32_t data = 0;

      bool success = true;

      while (address < address_end) {
        memcpy(&data, ram_eeprom + offset, sizeof(uint32_t));
        status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data);
        if (status == HAL_OK) {
          address += sizeof(uint32_t);
          offset += sizeof(uint32_t);
        }
        else {
          DEBUG_ECHOLNPAIR("HAL_FLASH_Program=", status);
          DEBUG_ECHOLNPAIR("GetError=", HAL_FLASH_GetError());
          DEBUG_ECHOLNPAIR("address=", address);
          success = false;
          break;
        }
      }

      LOCK_FLASH();

      if (success) {
        eeprom_data_written = false;
        DEBUG_ECHOLNPAIR("EEPROM saved to slot ", current_slot, ".");
      }

      return success;

    #else
      // The following was written for the STM32F4 but may work with other MCUs as well.
      // Most STM32F4 flash does not allow reading from flash during erase operations.
      // This takes about a second on a STM32F407 with a 128kB sector used as EEPROM.
      // Interrupts during this time can have unpredictable results, such as killing Servo
      // output. Servo output still glitches with interrupts disabled, but recovers after the
      // erase.
      TERN_(HAS_PAUSE_SERVO_OUTPUT, PAUSE_SERVO_OUTPUT());
      DISABLE_ISRS();
      eeprom_buffer_flush();
      ENABLE_ISRS();
      TERN_(HAS_PAUSE_SERVO_OUTPUT, RESUME_SERVO_OUTPUT());

      eeprom_data_written = false;
    #endif
  }

  return true;
}

bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, uint16_t *crc) {
  while (size--) {
    uint8_t v = *value;
    #if ENABLED(FLASH_EEPROM_LEVELING)
      if (v != ram_eeprom[pos]) {
        ram_eeprom[pos] = v;
        eeprom_data_written = true;
      }
    #else
      if (v != eeprom_buffered_read_byte(pos)) {
        eeprom_buffered_write_byte(pos, v);
        eeprom_data_written = true;
      }
    #endif
    crc16(crc, &v, 1);
    pos++;
    value++;
  }
  return false;
}

bool PersistentStore::read_data(int &pos, uint8_t *value, size_t size, uint16_t *crc, const bool writing/*=true*/) {
  do {
    const uint8_t c = TERN(FLASH_EEPROM_LEVELING, ram_eeprom[pos], eeprom_buffered_read_byte(pos));
    if (writing) *value = c;
    crc16(crc, &c, 1);
    pos++;
    value++;
  } while (--size);
  return false;
}

#endif // FLASH_EEPROM_EMULATION
#endif // ARDUINO_ARCH_STM32 && !STM32GENERIC