Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View file

@ -0,0 +1,61 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "display.h"
#include "drivers/button_id.h"
#include "stm32f2xx_gpio.h"
#include <stdint.h>
#include <stdbool.h>
#define GPIO_Port_NULL ((GPIO_TypeDef *) 0)
#define GPIO_Pin_NULL ((uint16_t)0x0000)
typedef struct {
const char* const name; ///< Name for debugging purposes.
GPIO_TypeDef* const gpio; ///< One of GPIOX. For example, GPIOA.
const uint32_t gpio_pin; ///< One of GPIO_Pin_X.
} ButtonConfig;
typedef struct {
GPIO_TypeDef* const gpio; ///< One of GPIOX. For example, GPIOA.
const uint32_t gpio_pin; ///< One of GPIO_Pin_X.
} ButtonComConfig;
typedef struct {
GPIO_TypeDef* const gpio; ///< One of GPIOX. For example, GPIOA.
const uint32_t gpio_pin; ///< One of GPIO_Pin_X.
} InputConfig;
// Power Configuration
/////////////////////////////////////////////////////////////////////////////
typedef struct {
const InputConfig vusb_stat;
const bool wake_on_usb_power;
} BoardConfigPower;
// Button Configuration
/////////////////////////////////////////////////////////////////////////////
typedef struct {
const ButtonConfig buttons[NUM_BUTTONS];
const ButtonComConfig button_com;
} BoardConfigButton;
#include "board_definitions.h"

View file

@ -0,0 +1,43 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "drivers/button.h"
#include "util/misc.h"
#include "stm32f2xx_rcc.h"
#include "stm32f2xx_gpio.h"
#include "stm32f2xx_syscfg.h"
static const BoardConfigPower BOARD_CONFIG_POWER = {
.vusb_stat = {
.gpio = GPIOC,
.gpio_pin = GPIO_Pin_13,
},
.wake_on_usb_power = true
};
static const BoardConfigButton BOARD_CONFIG_BUTTON = {
.buttons = {
[BUTTON_ID_BACK] = { "Back", GPIOC, GPIO_Pin_3 },
[BUTTON_ID_UP] = { "Up", GPIOA, GPIO_Pin_2 },
[BUTTON_ID_SELECT] = { "Select", GPIOC, GPIO_Pin_6 },
[BUTTON_ID_DOWN] = { "Down", GPIOA, GPIO_Pin_1 },
},
.button_com = { GPIOA, GPIO_Pin_0 },
};

View file

@ -0,0 +1,30 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
// FIXME: PBL-21049 Fix platform abstraction and board definition scheme
#if BOARD_EV2_4
#include "board_ev2_4.h" // shipped as Pebble 1.0
#elif BOARD_BB2
#include "board_bb2.h"
#elif BOARD_V1_5
#include "board_v1_5.h" // prototypes for Pebble 1.3/Pebble 1.5
#elif BOARD_V2_0
#include "board_v2_0.h" // prototypes for Pebble 2.0
#else
#error "Unknown board definition"
#endif

View file

@ -0,0 +1,43 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "drivers/button.h"
#include "util/misc.h"
#include "stm32f2xx_rcc.h"
#include "stm32f2xx_gpio.h"
#include "stm32f2xx_syscfg.h"
static const BoardConfigPower BOARD_CONFIG_POWER = {
.vusb_stat = {
.gpio = GPIOC,
.gpio_pin = GPIO_Pin_12,
},
.wake_on_usb_power = false
};
static const BoardConfigButton BOARD_CONFIG_BUTTON = {
.buttons = {
[BUTTON_ID_BACK] = { "Back", GPIOC, GPIO_Pin_3 },
[BUTTON_ID_UP] = { "Up", GPIOA, GPIO_Pin_2 },
[BUTTON_ID_SELECT] = { "Select", GPIOC, GPIO_Pin_6 },
[BUTTON_ID_DOWN] = { "Down", GPIOA, GPIO_Pin_1 },
},
.button_com = { GPIOA, GPIO_Pin_0 }
};

View file

@ -0,0 +1,43 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "drivers/button.h"
#include "util/misc.h"
#include "stm32f2xx_rcc.h"
#include "stm32f2xx_gpio.h"
#include "stm32f2xx_syscfg.h"
static const BoardConfigPower BOARD_CONFIG_POWER = {
.vusb_stat = {
.gpio = GPIOC,
.gpio_pin = GPIO_Pin_13,
},
.wake_on_usb_power = true
};
static const BoardConfigButton BOARD_CONFIG_BUTTON = {
.buttons = {
[BUTTON_ID_BACK] = { "Back", GPIOC, GPIO_Pin_3 },
[BUTTON_ID_UP] = { "Up", GPIOA, GPIO_Pin_2 },
[BUTTON_ID_SELECT] = { "Select", GPIOC, GPIO_Pin_6 },
[BUTTON_ID_DOWN] = { "Down", GPIOA, GPIO_Pin_1 },
},
.button_com = { GPIOA, GPIO_Pin_0 },
};

View file

@ -0,0 +1,43 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "drivers/button.h"
#include "util/misc.h"
#include "stm32f2xx_rcc.h"
#include "stm32f2xx_gpio.h"
#include "stm32f2xx_syscfg.h"
static const BoardConfigPower BOARD_CONFIG_POWER = {
.vusb_stat = {
.gpio = GPIOC,
.gpio_pin = GPIO_Pin_13,
},
.wake_on_usb_power = true
};
static const BoardConfigButton BOARD_CONFIG_BUTTON = {
.buttons = {
[BUTTON_ID_BACK] = { "Back", GPIOC, GPIO_Pin_3 },
[BUTTON_ID_UP] = { "Up", GPIOA, GPIO_Pin_2 },
[BUTTON_ID_SELECT] = { "Select", GPIOC, GPIO_Pin_6 },
[BUTTON_ID_DOWN] = { "Down", GPIOA, GPIO_Pin_1 },
},
.button_com = { GPIOA, GPIO_Pin_0 },
};

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#define DISP_COLS 144
#define DISP_ROWS 168
#define PBL_DISP_SHAPE_RECT

View file

@ -0,0 +1,80 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "boot_tests.h"
#include "board/board.h"
#include "drivers/button.h"
#include "drivers/dbgserial.h"
#include "drivers/flash.h"
#include "system/bootbits.h"
#include "system/rtc_registers.h"
#include "util/misc.h"
#include "stm32f2xx.h"
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
static const int STUCK_BUTTON_THRESHOLD = 5;
bool is_button_stuck(void) {
// We store how many times each button has been pressed on previous boots in this
// rtc backup register. Every time when we boot without that button pressed that
// counter gets cleared. If the byte reaches 5, return a failure.
uint32_t button_counter_register = RTC_ReadBackupRegister(STUCK_BUTTON_REGISTER);
uint8_t* button_counter = (uint8_t*) (&button_counter_register);
bool result = false;
for (int button_id = 0; button_id < NUM_BUTTONS; button_id++) {
if (!button_is_pressed(button_id)) {
button_counter[button_id] = 0;
continue;
}
if (button_counter[button_id] >= STUCK_BUTTON_THRESHOLD) {
dbgserial_putstr("Stuck button register is invalid, clearing.");
dbgserial_print_hex(button_counter_register);
RTC_WriteBackupRegister(STUCK_BUTTON_REGISTER, 0);
return false;
}
button_counter[button_id] += 1;
if (button_counter[button_id] >= STUCK_BUTTON_THRESHOLD) {
dbgserial_print("Button id ");
dbgserial_print_hex(button_id);
dbgserial_putstr("is stuck!");
result = true;
}
}
if (button_counter_register != 0) {
dbgserial_print("Button was pushed on boot. Button counter: ");
dbgserial_print_hex(button_counter_register);
dbgserial_newline();
}
RTC_WriteBackupRegister(STUCK_BUTTON_REGISTER, button_counter_register);
return result;
}
bool is_flash_broken(void) {
return !flash_sanity_check();
}

View file

@ -0,0 +1,22 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
bool is_button_stuck(void);
bool is_flash_broken(void);

View file

@ -0,0 +1,27 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "button_id.h"
#include <stdbool.h>
#include <stdint.h>
void button_init(void);
bool button_is_pressed(ButtonId id);
uint8_t button_get_state_bits(void);

View file

@ -0,0 +1,41 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
//! @addtogroup UI
//! @{
//! @addtogroup Clicks
//! \brief Dealing with button input
//! @{
//! Button ID values
//! @see \ref click_recognizer_get_button_id()
typedef enum {
//! Back button
BUTTON_ID_BACK = 0,
//! Up button
BUTTON_ID_UP,
//! Select (middle) button
BUTTON_ID_SELECT,
//! Down button
BUTTON_ID_DOWN,
//! Total number of buttons
NUM_BUTTONS
} ButtonId;
//! @} // end addtogroup Clicks
//! @} // end addtogroup UI

View file

@ -0,0 +1,31 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
/*
* calculate the CRC32 for a stream of bytes.
* NOTE: not safe to call from ISR
*/
uint32_t crc_calculate_bytes(const uint8_t* data, unsigned int data_length);
/*
* calculate the CRC32 for a stream of bytes from flash
* NOTE: not safe to call from ISR
*/
uint32_t crc_calculate_flash(uint32_t address, unsigned int num_bytes);

View file

@ -0,0 +1,34 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
void dbgserial_init(void);
void dbgserial_putstr(const char* str);
void dbgserial_newline(void);
//! Like dbgserial_putstr, but without a terminating newline
void dbgserial_print(const char* str);
void dbgserial_print_hex(uint32_t value);
void dbgserial_putstr_fmt(char* buffer, unsigned int buffer_size, const char* fmt, ...)
__attribute__((format(printf, 3, 4)));

View file

@ -0,0 +1,34 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdint.h>
void display_init(void);
void display_boot_splash(void);
void display_error_code(uint32_t);
//! Do whatever is necessary to prevent visual artifacts when resetting
//! the watch.
void display_prepare_for_reset(void);
//! Display the progress of a firmware update.
//!
//! The progress is expressed as a rational number less than or equal to 1.
//! When numerator == denominator, the progress indicator shows that the update
//! is complete.
void display_firmware_update_progress(uint32_t numerator, uint32_t denominator);

View file

@ -0,0 +1,47 @@
#define dead_face_width 92
#define dead_face_height 44
static const unsigned char dead_face_bits[] = {
0x00, 0x0c, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x30, 0x00,
0x00, 0x18, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x18, 0x00,
0x00, 0x30, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x0c, 0x00,
0x00, 0x60, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0x00,
0x00, 0xc0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x00,
0x00, 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x01, 0x00,
0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00,
0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00,
0x00, 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x01, 0x00,
0x00, 0xc0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x00,
0x00, 0x60, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0x00,
0x00, 0x30, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x0c, 0x00,
0x00, 0x18, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x18, 0x00,
0x00, 0x0c, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x30, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0x80, 0x7f, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x1f, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00,
0x00, 0x00, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0f, 0x00, 0x00,
0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00,
0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00,
0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00,
0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,
0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00,
0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00,
0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00,
0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00,
0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01,
0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c,
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 };

View file

@ -0,0 +1,11 @@
#define empty_bar_width 96
#define empty_bar_height 8
static const unsigned char empty_bar_bits[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };

View file

@ -0,0 +1,20 @@
#define error_url_width 109
#define error_url_height 14
static const unsigned char error_url_bits[] = {
0x00, 0x00, 0x06, 0x83, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x06, 0x83, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x83, 0x01, 0x00, 0x00, 0x00,
0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x78, 0x36, 0x9b, 0x79, 0xc0,
0xe3, 0xd9, 0x0c, 0x8c, 0x67, 0xdb, 0x3c, 0x1b, 0xf8, 0xfd, 0x7e, 0xbf,
0xfd, 0xe0, 0xf7, 0xfb, 0x1f, 0xc6, 0xef, 0xfb, 0x7e, 0x1f, 0x98, 0xcd,
0x66, 0xb3, 0xcd, 0x60, 0x36, 0x9b, 0x19, 0xc6, 0x6c, 0x18, 0x66, 0x03,
0x98, 0xfd, 0x66, 0xb3, 0xfd, 0x60, 0x30, 0x9b, 0x19, 0xc6, 0x6f, 0x18,
0x66, 0x03, 0x98, 0xfd, 0x66, 0xb3, 0xfd, 0x60, 0x30, 0x9b, 0x19, 0xc3,
0x6f, 0x18, 0x66, 0x03, 0x98, 0x0d, 0x66, 0xb3, 0x0d, 0x60, 0x36, 0x9b,
0x19, 0xc3, 0x60, 0x18, 0x66, 0x03, 0xf8, 0xfd, 0x7e, 0xbf, 0xfd, 0xec,
0xf7, 0x9b, 0x19, 0xc3, 0x6f, 0x18, 0x7e, 0x03, 0xd8, 0x78, 0x36, 0x9b,
0x79, 0xcc, 0xe3, 0x99, 0x99, 0x81, 0x67, 0x18, 0x3c, 0x03, 0x18, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00,
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00 };

View file

@ -0,0 +1,103 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
// hex_digits_bits is indexed on the digit:
// ie: hex_digits_bits[0] is the bits to display 0 on
// the screen stored in xbm format
static const uint8_t hex_digits_bits[][36] = {
{
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
},
{
0x38, 0x00, 0x3C, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x38, 0x00, 0x38, 0x00,
0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00,
0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00,
},
{
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC0, 0x01,
0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00,
0x0E, 0x00, 0x0F, 0x00, 0x07, 0x00, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01,
},
{
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC0, 0x01,
0xC0, 0x01, 0xF8, 0x00, 0x78, 0x00, 0xF8, 0x00, 0xC0, 0x01, 0xC0, 0x01,
0xC0, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
},
{
0xE0, 0x00, 0xE0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xF8, 0x00, 0xF8, 0x00,
0xF8, 0x00, 0xFC, 0x00, 0xEC, 0x00, 0xEE, 0x00, 0xE6, 0x00, 0xFF, 0x01,
0xFF, 0x01, 0xFF, 0x01, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00,
},
{
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00,
0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC0, 0x01, 0xC0, 0x01,
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
},
{
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0x07, 0x00,
0x07, 0x00, 0x77, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01,
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
},
{
0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xE0, 0x00, 0xE0, 0x00, 0x70, 0x00,
0x70, 0x00, 0x70, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00,
0x1C, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x1C, 0x00,
},
{
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
0xC7, 0x01, 0xFE, 0x00, 0x7C, 0x00, 0xFE, 0x00, 0xC7, 0x01, 0xC7, 0x01,
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
},
{
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x01, 0xDC, 0x01, 0xC0, 0x01,
0xC0, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
},
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0xFE, 0x00,
0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xF0, 0x01, 0xFC, 0x01, 0xCE, 0x01,
0xC7, 0x01, 0xC7, 0x01, 0xE7, 0x01, 0xFF, 0x01, 0xDF, 0x01, 0xCE, 0x01,
},
{
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0xE7, 0x00, 0xF7, 0x01,
0xFF, 0x01, 0xCF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
0xC7, 0x01, 0xC7, 0x01, 0xCF, 0x01, 0xFF, 0x01, 0xF7, 0x01, 0xE7, 0x00,
},
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0xFE, 0x00,
0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00,
0x07, 0x00, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
},
{
0xC0, 0x01, 0xC0, 0x01, 0xC0, 0x01, 0xC0, 0x01, 0xCE, 0x01, 0xDF, 0x01,
0xFF, 0x01, 0xE7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
0xC7, 0x01, 0xC7, 0x01, 0xE7, 0x01, 0xFF, 0x01, 0xDF, 0x01, 0xCE, 0x01,
},
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0xFE, 0x00,
0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFF, 0x01,
0x07, 0x00, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
},
{
0xE0, 0x00, 0xF0, 0x00, 0xF8, 0x00, 0x38, 0x00, 0xFE, 0x00, 0xFE, 0x00,
0xFE, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00,
0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00,
}
};

View file

@ -0,0 +1,35 @@
#define pebble_logo_width 105
#define pebble_logo_height 27
static const unsigned char pebble_logo_bits[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x03, 0x00, 0x18, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x03, 0x00,
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00,
0x03, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
0x00, 0x00, 0x03, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x60, 0x00, 0x00, 0x03, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0xE0,
0x03, 0x00, 0x1F, 0x60, 0xF8, 0x00, 0xC3, 0x07, 0x18, 0xC0, 0x07, 0x00,
0x00, 0xF8, 0x0F, 0xC0, 0x7F, 0x60, 0xFE, 0x03, 0xF3, 0x1F, 0x18, 0xF0,
0x1F, 0x00, 0x00, 0x0C, 0x18, 0x60, 0xC0, 0x60, 0x03, 0x06, 0x1B, 0x30,
0x18, 0x18, 0x30, 0x00, 0x00, 0x06, 0x30, 0x30, 0x80, 0xE1, 0x01, 0x0C,
0x0F, 0x60, 0x18, 0x0C, 0x60, 0x00, 0x00, 0x03, 0x60, 0x18, 0x00, 0xE3,
0x00, 0x18, 0x07, 0xC0, 0x18, 0x06, 0xC0, 0x00, 0x80, 0x01, 0x40, 0x0C,
0x00, 0x62, 0x00, 0x10, 0x03, 0x80, 0x18, 0x03, 0x80, 0x00, 0x80, 0x01,
0xC0, 0x0C, 0x00, 0x66, 0x00, 0x30, 0x03, 0x80, 0x19, 0x03, 0x80, 0x01,
0x80, 0x01, 0xC0, 0xFC, 0xFF, 0x67, 0x00, 0x30, 0x03, 0x80, 0x19, 0xFF,
0xFF, 0x01, 0x80, 0x01, 0xC0, 0xFC, 0xFF, 0x63, 0x00, 0x30, 0x03, 0x80,
0x19, 0xFF, 0xFF, 0x00, 0x80, 0x01, 0xC0, 0x0C, 0x00, 0x60, 0x00, 0x30,
0x03, 0x80, 0x19, 0x03, 0x00, 0x00, 0x80, 0x01, 0xC0, 0x0C, 0x00, 0x60,
0x00, 0x30, 0x03, 0x80, 0x19, 0x03, 0x00, 0x00, 0x80, 0x01, 0x40, 0x0C,
0x00, 0x62, 0x00, 0x10, 0x03, 0x80, 0x18, 0x03, 0x80, 0x00, 0x80, 0x03,
0x60, 0x18, 0x00, 0xC3, 0x00, 0x18, 0x06, 0xC0, 0x18, 0x06, 0xC0, 0x00,
0x80, 0x07, 0x30, 0x30, 0x80, 0x81, 0x01, 0x0C, 0x0C, 0x60, 0x18, 0x0C,
0x60, 0x00, 0x80, 0x0D, 0x18, 0x60, 0xC0, 0x00, 0x03, 0x06, 0x18, 0x30,
0x38, 0x18, 0x30, 0x00, 0x80, 0xF9, 0x0F, 0xC0, 0x7F, 0x00, 0xFE, 0x03,
0xF0, 0x1F, 0x70, 0xF0, 0x1F, 0x00, 0x80, 0xE1, 0x03, 0x00, 0x1F, 0x00,
0xF8, 0x00, 0xC0, 0x07, 0x60, 0xC0, 0x07, 0x00, 0x80, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };

View file

@ -0,0 +1,425 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "board/display.h"
#include "drivers/periph_config.h"
#include "drivers/gpio.h"
#include "drivers/dbgserial.h"
#include "util/attributes.h"
#include "util/delay.h"
#include "stm32f2xx_dma.h"
#include "stm32f2xx_gpio.h"
#include "stm32f2xx_rcc.h"
#include "stm32f2xx_spi.h"
#include <stdbool.h>
#include <stdint.h>
// Bootloader images
#include "drivers/display/resources/hex_digits.h"
#include "drivers/display/resources/dead_face.xbm"
#include "drivers/display/resources/empty_bar.xbm"
#include "drivers/display/resources/error_url.xbm"
#include "drivers/display/resources/pebble_logo.xbm"
#define DISP_LINE_BYTES (DISP_COLS / 8)
#define DISP_LINE_WORDS (((DISP_COLS - 1) / 32) + 1)
// GPIO constants
#define DISP_SPI (SPI2)
#define DISP_GPIO (GPIOB)
#define PWR_CTL_GPIO (GPIOC)
#define PWR_CTL_PIN (GPIO_Pin_5)
#define DISPLAY_SPI_CLOCK (RCC_APB1Periph_SPI2)
#define DISP_PIN_VCOM (GPIO_Pin_1)
#define DISP_PINSOURCE_VCOM (GPIO_PinSource1)
#define DISP_PIN_SCS (GPIO_Pin_12)
#define DISP_PIN_SCLK (GPIO_Pin_13)
#define DISP_PIN_LCD (GPIO_Pin_14)
#define DISP_PIN_SI (GPIO_Pin_15)
#define DISP_MODE_STATIC (0x00)
#define DISP_MODE_WRITE (0x80)
#define DISP_MODE_CLEAR (0x20)
// The bootloader leaves SYSCLK at defaults (connected to HSI at 16 Mhz),
// and there are no prescalers on any of the buses. Since the display
// can handle a max of 2 Mhz, we want to divide by 8
#define DISPLAY_PERIPH_PRESCALER (SPI_BaudRatePrescaler_8)
static void prv_enable_display_spi_clock() {
periph_config_enable(RCC_APB1PeriphClockCmd, DISPLAY_SPI_CLOCK);
}
static void prv_disable_display_spi_clock() {
periph_config_disable(RCC_APB1PeriphClockCmd, DISPLAY_SPI_CLOCK);
}
static void prv_enable_chip_select(void) {
gpio_use(DISP_GPIO);
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_SET);
// required setup time > 3us
delay_us(7);
gpio_release(DISP_GPIO);
}
static void prv_disable_chip_select(void) {
gpio_use(DISP_GPIO);
// delay while last byte is emitted by the SPI peripheral
delay_us(7);
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_RESET);
// hold time > 1us
// produce a delay 4ms
delay_us(4);
gpio_release(DISP_GPIO);
}
//! These functions needed to be called around any commands that
//! are sent to the display. NOINLINE only for code size savings.
static NOINLINE void prv_enable_display_access(void) {
prv_enable_display_spi_clock();
prv_enable_chip_select();
}
static NOINLINE void prv_disable_display_access(void) {
prv_disable_chip_select();
prv_disable_display_spi_clock();
}
//! Write a single byte synchronously to the display. This is the only practical
//! way to write to the display in the bootloader since we don't have interrupts.
static void prv_display_write_byte(uint8_t d) {
// Block until the tx buffer is empty
SPI_I2S_SendData(DISP_SPI, d);
while (!SPI_I2S_GetFlagStatus(DISP_SPI, SPI_I2S_FLAG_TXE)) {}
}
// Since all these values are constant we can save code space
// by storing the initialized struct in memory rather than
// needing to copy in each value
static GPIO_InitTypeDef s_disp_gpio_init = {
.GPIO_OType = GPIO_OType_PP,
.GPIO_PuPd = GPIO_PuPd_NOPULL,
.GPIO_Mode = GPIO_Mode_AF,
.GPIO_Speed = GPIO_Speed_50MHz,
.GPIO_Pin = DISP_PIN_SCLK | DISP_PIN_SI
};
static SPI_InitTypeDef s_disp_spi_init = {
.SPI_Direction = SPI_Direction_1Line_Tx, // Write-only SPI
.SPI_Mode = SPI_Mode_Master,
.SPI_DataSize = SPI_DataSize_8b,
.SPI_CPOL = SPI_CPOL_Low,
.SPI_CPHA = SPI_CPHA_1Edge,
.SPI_NSS = SPI_NSS_Soft,
// We want the SPI clock to run at 2MHz
.SPI_BaudRatePrescaler = DISPLAY_PERIPH_PRESCALER,
// MSB order allows us to write pixels out without reversing bytes, but command bytes
// have to be reversed
.SPI_FirstBit = SPI_FirstBit_MSB,
.SPI_CRCPolynomial = 7 // default
};
//! Setup TIM3 to pulse VCOM every second to avoid damage to the display
void prv_setup_pulse_vcom(void) {
periph_config_enable(RCC_APB1PeriphClockCmd, RCC_APB1Periph_TIM3);
// Standard peripheral functions are too big so toggle the registers manually:
// Scale timer to roll over once per second
TIM3->CR1 = TIM_CounterMode_Up | TIM_CKD_DIV1 | TIM_CR1_ARPE;
// Prescaler divides by PSC + 1, which gives us 64000 ticks per second
// in the timer. This prescaler is the smallest divisor that still allows the
// timer to roll over exactly once per second (since the timer is 16 bits)
TIM3->PSC = 249;
// The timer reloads one cycle after ARR is reached, so ARR of
// 63999 means the timer will roll over once per second
TIM3->ARR = 63999;
// Enable the preload register and put us in PWM mode 2
TIM3->CCMR2 = TIM_CCMR2_OC4PE | (TIM_OCMode_PWM2 << 8);
// The timer forces the pin high when the counter is greater than
// or equal to this value. Since ARR is 63999, this means the
// pin will be high for exactly one tick of the timer (~16 us)
TIM3->CCR4 = 63999;
// Enable channel 4
TIM3->CCER = TIM_CCER_CC4E;
// Hook up the VCOM pin's alternate function to TIM3
GPIO_PinAFConfig(DISP_GPIO, DISP_PINSOURCE_VCOM, GPIO_AF_TIM3);
TIM3->EGR = TIM_PSCReloadMode_Immediate; // Reload shadow registers
TIM_Cmd(TIM3, ENABLE);
}
static void prv_display_start(void) {
// Enable the GPIO{B,C} clocks; this is required before configuring the pins
gpio_use(DISP_GPIO);
gpio_use(PWR_CTL_GPIO);
// Connect PB13 to SPI2_SCK
GPIO_PinAFConfig(DISP_GPIO, GPIO_PinSource13, GPIO_AF_SPI2);
// Connect PB15 to SPI2_MOSI
GPIO_PinAFConfig(DISP_GPIO, GPIO_PinSource15, GPIO_AF_SPI2);
GPIO_Init(DISP_GPIO, &s_disp_gpio_init);
s_disp_gpio_init.GPIO_Mode = GPIO_Mode_OUT;
s_disp_gpio_init.GPIO_OType = GPIO_OType_OD;
s_disp_gpio_init.GPIO_Pin = PWR_CTL_PIN;
GPIO_Init(PWR_CTL_GPIO, &s_disp_gpio_init);
s_disp_gpio_init.GPIO_Mode = GPIO_Mode_OUT;
s_disp_gpio_init.GPIO_OType = GPIO_OType_PP;
s_disp_gpio_init.GPIO_Pin = DISP_PIN_SCS;
GPIO_Init(DISP_GPIO, &s_disp_gpio_init);
s_disp_gpio_init.GPIO_Mode = GPIO_Mode_AF;
s_disp_gpio_init.GPIO_Pin = DISP_PIN_VCOM;
GPIO_Init(DISP_GPIO, &s_disp_gpio_init);
s_disp_gpio_init.GPIO_Mode = GPIO_Mode_OUT;
s_disp_gpio_init.GPIO_OType = GPIO_OType_OD;
s_disp_gpio_init.GPIO_Pin = DISP_PIN_LCD;
GPIO_Init(DISP_GPIO, &s_disp_gpio_init);
// Set up a SPI bus on SPI2
SPI_I2S_DeInit(DISP_SPI);
SPI_Init(DISP_SPI, &s_disp_spi_init);
SPI_Cmd(DISP_SPI, ENABLE);
// +5V to 5V_EN pin
GPIO_WriteBit(PWR_CTL_GPIO, PWR_CTL_PIN, Bit_RESET);
// +5V to LCD pin (Set this pin low to turn off the display)
GPIO_WriteBit(DISP_GPIO, DISP_PIN_LCD, Bit_SET);
prv_setup_pulse_vcom();
// Don't need the GPIO peripheral clocks to be enabled anymore
gpio_release(PWR_CTL_GPIO);
gpio_release(DISP_GPIO);
}
// Clear-all mode is entered by sending 0x04 to the panel
void display_clear(void) {
prv_enable_display_access();
prv_display_write_byte(DISP_MODE_CLEAR);
prv_display_write_byte(0x00);
prv_disable_display_access();
}
//! Static mode is entered by sending 0x00 to the panel
//! This stops any further updates being registered by
//! the display, preventing corruption on shutdown / boot
static void prv_display_enter_static(void) {
prv_enable_display_access();
prv_display_write_byte(DISP_MODE_STATIC);
prv_display_write_byte(0x00);
prv_display_write_byte(0x00);
prv_disable_display_access();
}
// Helper to reverse command bytes
static uint8_t prv_reverse_bits(uint8_t input) {
uint8_t result;
__asm__ ("rev %[result], %[input]\n\t"
"rbit %[result], %[result]"
: [result] "=r" (result)
: [input] "r" (input));
return result;
}
static void prv_display_start_write(void) {
prv_enable_display_access();
prv_display_write_byte(DISP_MODE_WRITE);
}
static void prv_display_write_line(uint8_t line_addr, const uint8_t *line) {
// 1-indexed (ugh) 8bit line address (1-168)
prv_display_write_byte(prv_reverse_bits(168 - line_addr));
for (int i = DISP_LINE_BYTES - 1; i >= 0; --i) {
prv_display_write_byte(line[i]);
}
prv_display_write_byte(0x00);
}
static void prv_display_end_write(void) {
prv_display_write_byte(0x00);
prv_disable_display_access();
}
// Round a bit offset to a byte offset
static unsigned prv_round_to_byte(unsigned x) {
return (x + 7) >> 3;
}
// Draw bitmap onto buffer.
static void prv_draw_bitmap(const uint8_t *bitmap, unsigned x_offset, unsigned y_offset,
unsigned width, unsigned height,
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
// Need to convert offsets to bytes for the horizontal dimensions
x_offset = prv_round_to_byte(x_offset);
width = prv_round_to_byte(width);
for (unsigned i = 0; i < height; i++) {
memcpy(buffer[i + y_offset] + x_offset, bitmap + i * width, width);
}
}
static void prv_display_buffer(uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
prv_display_start_write();
for (int i = 0; i < DISP_ROWS; i++) {
prv_display_write_line(i, buffer[i]);
}
prv_display_end_write();
}
void display_boot_splash(void) {
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES];
// Draw black
memset(buffer, 0x00, sizeof(buffer));
prv_draw_bitmap(pebble_logo_bits, 16, 64, pebble_logo_width, pebble_logo_height, buffer);
prv_display_buffer(buffer);
}
static void prv_set_bit(uint8_t x, uint8_t y, uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
buffer[y][x / 8] |= (1 << (x % 8));
}
static void prv_render_char(unsigned digit, uint8_t x_offset_bits, uint8_t y_offset,
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
const unsigned char_rows = 18, char_cols = 9;
const uint8_t * char_data = hex_digits_bits[digit];
// Each character requires 2 bytes of storage
for (unsigned y = 0; y < char_rows; y++) {
unsigned cur_y = y_offset + y;
uint8_t first_byte = char_data[2 * y];
for (unsigned x = 0; x < char_cols; x++) {
bool pixel;
if (x < 8) { // Pixel is in first byte
pixel = first_byte & (1 << x);
}
else { // Last pixel is in second byte
pixel = char_data[2 * y + 1] & 1;
}
// The buffer is already all black, so just set the white pixels
if (pixel) {
prv_set_bit(x_offset_bits + x, cur_y, buffer);
}
}
}
}
static void prv_draw_code(uint32_t code, uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
const unsigned y_offset = 116; // beneath sad face, above url
unsigned x_offset = 28; // Aligned with sad face
// Extract and print digits
for (int i = 7; i >= 0; i--) {
// Mask off 4 bits at a time
uint32_t mask = (0xf << (i * 4));
unsigned digit = ((code & mask) >> (i * 4));
prv_render_char(digit, x_offset, y_offset, buffer);
// Each character is 9px wide plus 2px of padding
x_offset += 11;
}
}
void display_error_code(uint32_t code) {
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES];
memset(buffer, 0x00, sizeof(buffer));
prv_draw_bitmap(dead_face_bits, 24, 32, dead_face_width, dead_face_height, buffer);
prv_draw_code(code, buffer);
prv_draw_bitmap(error_url_bits, 16, 144, error_url_width, error_url_height, buffer);
prv_display_buffer(buffer);
}
//! Do whatever is necessary to prevent visual artifacts when resetting
//! the watch.
void display_prepare_for_reset(void) {
prv_display_enter_static();
}
//! Display the progress of a firmware update.
//!
//! The progress is expressed as a rational number less than or equal to 1.
//! When numerator == denominator, the progress indicator shows that the update
//! is complete.
void display_firmware_update_progress(uint32_t numerator, uint32_t denominator) {
// Dimensions for progress bar
const unsigned x_offset = 24, y_offset = 106,
inner_bar_width = 94, inner_bar_height = 6;
static unsigned s_prev_num_pixels = -1;
// Calculate number of pixels to fill in
unsigned num_pixels = inner_bar_width * numerator / denominator;
if (num_pixels == s_prev_num_pixels) {
return;
}
s_prev_num_pixels = num_pixels;
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES];
memset(buffer, 0x00, sizeof(buffer));
prv_draw_bitmap(pebble_logo_bits, 16, 64, pebble_logo_width, pebble_logo_height, buffer);
prv_draw_bitmap(empty_bar_bits, x_offset, y_offset, empty_bar_width, empty_bar_height, buffer);
for (unsigned y = 0; y < inner_bar_height; y++) {
for (unsigned x = 0; x < num_pixels; x++) {
// Add 1 to offsets so we don't write into outer box
prv_set_bit(x + x_offset + 1, y_offset + y + 1, buffer);
}
}
prv_display_buffer(buffer);
}
void display_init(void) {
prv_enable_display_spi_clock();
prv_display_start();
prv_disable_display_spi_clock();
}

View file

@ -0,0 +1,37 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
//! Configure the micro's peripherals to communicate with the flash chip
void flash_init(void);
//! Read 1 or more bytes starting at the specified 24bit address into
//! the provided buffer. This function does no range checking, so it is
//! currently possible to run off the end of the flash.
//!
//! @param buffer A byte-buffer that will be used to store the data
//! read from flash.
//! @param start_addr The address of the first byte to be read from flash.
//! @param buffer_size The total number of bytes to be read from flash.
void flash_read_bytes(uint8_t* buffer, uint32_t start_addr, uint32_t buffer_size);
//! Check if we can talk to the flash.
//! @return true if the CFI table can be queried.
bool flash_sanity_check(void);

View file

@ -0,0 +1,258 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdbool.h>
#include <stdint.h>
#include "drivers/flash.h"
#include "drivers/gpio.h"
#include "drivers/periph_config.h"
#include "stm32f2xx_gpio.h"
#include "util/delay.h"
static const uint32_t EXPECTED_SPI_FLASH_ID_32MBIT = 0x20bb16;
static const uint32_t EXPECTED_SPI_FLASH_ID_64MBIT = 0x20bb17;
// Serial-flash commands
static const uint8_t FLASH_CMD_WRITE_ENABLE = 0x06;
static const uint8_t FLASH_CMD_WRITE_DISABLE = 0x04;
static const uint8_t FLASH_CMD_READ_STATUS_REG = 0x05;
static const uint8_t FLASH_CMD_READ = 0x03;
static const uint8_t FLASH_CMD_READ_ID = 0x9F;
static const uint8_t FLASH_CMD_DEEP_SLEEP = 0xB9;
static const uint8_t FLASH_CMD_WAKE = 0xAB;
static const uint8_t FLASH_CMD_DUMMY = 0xA9;
static const struct {
SPI_TypeDef *const spi;
GPIO_TypeDef *const spi_gpio;
uint8_t scs_pin, sclk_pin, miso_pin, mosi_pin;
} FLASH_CONFIG = {
.spi = SPI1,
.spi_gpio = GPIOA,
.scs_pin = 4,
.sclk_pin = 5,
.miso_pin = 6,
.mosi_pin = 7,
};
static void prv_enable_flash_spi_clock(void) {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
}
static void prv_disable_flash_spi_clock(void) {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, DISABLE);
}
static void prv_flash_start(void) {
gpio_use(FLASH_CONFIG.spi_gpio);
// Enable the GPIOA clock
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
uint8_t altfunc = GPIO_AF_SPI1;
// Connect pins to their SPI functionality
GPIO_PinAFConfig(FLASH_CONFIG.spi_gpio, FLASH_CONFIG.sclk_pin, altfunc);
GPIO_PinAFConfig(FLASH_CONFIG.spi_gpio, FLASH_CONFIG.miso_pin, altfunc);
GPIO_PinAFConfig(FLASH_CONFIG.spi_gpio, FLASH_CONFIG.mosi_pin, altfunc);
// Setup MISO/MOSI
GPIO_InitTypeDef gpio_cfg;
gpio_cfg.GPIO_OType = GPIO_OType_PP;
gpio_cfg.GPIO_PuPd = GPIO_PuPd_NOPULL;
gpio_cfg.GPIO_Mode = GPIO_Mode_AF;
gpio_cfg.GPIO_Speed = GPIO_Speed_50MHz;
gpio_cfg.GPIO_Pin = (1 << FLASH_CONFIG.miso_pin) | (1 << FLASH_CONFIG.mosi_pin);
GPIO_Init(FLASH_CONFIG.spi_gpio, &gpio_cfg);
// Configure the SCLK pin to have a weak pull-down to put it in a known state
// when SCS is toggled
gpio_cfg.GPIO_PuPd = GPIO_PuPd_DOWN;
gpio_cfg.GPIO_Pin = (1 << FLASH_CONFIG.sclk_pin);
GPIO_Init(FLASH_CONFIG.spi_gpio, &gpio_cfg);
// Configure SCS to be controlled in software; pull up to high when inactive
gpio_cfg.GPIO_Mode = GPIO_Mode_OUT;
gpio_cfg.GPIO_Pin = (1 << FLASH_CONFIG.scs_pin);
gpio_cfg.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(FLASH_CONFIG.spi_gpio, &gpio_cfg);
// Set up a SPI bus on SPI1
SPI_InitTypeDef spi_cfg;
SPI_I2S_DeInit(FLASH_CONFIG.spi);
spi_cfg.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
spi_cfg.SPI_Mode = SPI_Mode_Master;
spi_cfg.SPI_DataSize = SPI_DataSize_8b;
spi_cfg.SPI_CPOL = SPI_CPOL_Low;
spi_cfg.SPI_CPHA = SPI_CPHA_1Edge;
spi_cfg.SPI_NSS = SPI_NSS_Soft;
// APB2 is at 16MHz, max is 54MHz, so we want the smallest prescaler
spi_cfg.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
spi_cfg.SPI_FirstBit = SPI_FirstBit_MSB;
spi_cfg.SPI_CRCPolynomial = 7;
SPI_Init(FLASH_CONFIG.spi, &spi_cfg);
SPI_Cmd(FLASH_CONFIG.spi, ENABLE);
gpio_release(FLASH_CONFIG.spi_gpio);
}
static void prv_flash_start_cmd(void) {
gpio_use(FLASH_CONFIG.spi_gpio);
GPIO_ResetBits(FLASH_CONFIG.spi_gpio, 1 << FLASH_CONFIG.scs_pin);
gpio_release(FLASH_CONFIG.spi_gpio);
}
static void prv_flash_end_cmd(void) {
gpio_use(FLASH_CONFIG.spi_gpio);
GPIO_SetBits(FLASH_CONFIG.spi_gpio, 1 << FLASH_CONFIG.scs_pin);
gpio_release(FLASH_CONFIG.spi_gpio);
// 50ns required between SCS going high and low again, so just delay here to be safe
delay_us(1);
}
static uint8_t prv_flash_send_and_receive_byte(uint8_t byte) {
// Ensure that there are no other write operations in progress
while (SPI_I2S_GetFlagStatus(FLASH_CONFIG.spi, SPI_I2S_FLAG_TXE) == RESET) {}
// Send the byte on the SPI bus
SPI_I2S_SendData(FLASH_CONFIG.spi, byte);
// Wait for the response byte to be received
while (SPI_I2S_GetFlagStatus(FLASH_CONFIG.spi, SPI_I2S_FLAG_RXNE) == RESET) {}
// Return the byte
return SPI_I2S_ReceiveData(FLASH_CONFIG.spi);
}
static void prv_flash_send_24b_address(uint32_t start_addr) {
// Ensure the high bits are not set.
prv_flash_send_and_receive_byte((start_addr & 0xFF0000) >> 16);
prv_flash_send_and_receive_byte((start_addr & 0x00FF00) >> 8);
prv_flash_send_and_receive_byte((start_addr & 0x0000FF));
}
static uint8_t prv_flash_read_next_byte(void) {
uint8_t result = prv_flash_send_and_receive_byte(FLASH_CMD_DUMMY);
return result;
}
static void prv_flash_wait_for_write_bounded(volatile int cycles_to_wait) {
prv_flash_start_cmd();
prv_flash_send_and_receive_byte(FLASH_CMD_READ_STATUS_REG);
uint8_t status_register = 0;
do {
if (cycles_to_wait-- < 1) {
break;
}
status_register = prv_flash_read_next_byte();
} while (status_register & 0x1);
prv_flash_end_cmd();
}
static void prv_flash_wait_for_write(void) {
prv_flash_start_cmd();
prv_flash_send_and_receive_byte(FLASH_CMD_READ_STATUS_REG);
uint8_t status_register = 0;
do {
status_register = prv_flash_read_next_byte();
} while (status_register & 0x1);
prv_flash_end_cmd();
}
static void prv_flash_deep_sleep_exit(void) {
prv_flash_start_cmd();
prv_flash_send_and_receive_byte(FLASH_CMD_WAKE);
prv_flash_end_cmd();
// wait a sufficient amount of time to enter standby mode
// It appears violating these timing conditions can lead to
// random bit corruptions on flash writes!
delay_us(100);
}
static uint32_t prv_flash_whoami(void) {
prv_enable_flash_spi_clock();
prv_flash_wait_for_write_bounded(64000000);
prv_flash_start_cmd();
prv_flash_send_and_receive_byte(FLASH_CMD_READ_ID);
uint32_t manufacturer = prv_flash_read_next_byte();
uint32_t type = prv_flash_read_next_byte();
uint32_t capacity = prv_flash_read_next_byte();
prv_flash_end_cmd();
prv_disable_flash_spi_clock();
return ((manufacturer << 16) | (type << 8) | capacity);
}
static bool prv_check_whoami(uint32_t spi_flash_id) {
return spi_flash_id == EXPECTED_SPI_FLASH_ID_32MBIT ||
spi_flash_id == EXPECTED_SPI_FLASH_ID_64MBIT;
}
static bool prv_is_whoami_correct(void) {
uint32_t spi_flash_id = prv_flash_whoami();
return prv_check_whoami(spi_flash_id);
}
void flash_init(void) {
prv_enable_flash_spi_clock();
prv_flash_start();
// Assume that last time we shut down we were asleep. Come back out.
prv_flash_deep_sleep_exit();
prv_disable_flash_spi_clock();
prv_flash_whoami();
}
bool flash_sanity_check(void) {
return prv_is_whoami_correct();
}
void flash_read_bytes(uint8_t* buffer, uint32_t start_addr, uint32_t buffer_size) {
if (!buffer_size) {
return;
}
prv_enable_flash_spi_clock();
prv_flash_wait_for_write();
prv_flash_start_cmd();
prv_flash_send_and_receive_byte(FLASH_CMD_READ);
prv_flash_send_24b_address(start_addr);
while (buffer_size--) {
*buffer = prv_flash_read_next_byte();
buffer++;
}
prv_flash_end_cmd();
prv_disable_flash_spi_clock();
}

View file

@ -0,0 +1,26 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include "stm32f2xx_gpio.h"
#include "board/board.h"
void gpio_use(GPIO_TypeDef* GPIOx);
void gpio_release(GPIO_TypeDef* GPIOx);

View file

@ -0,0 +1,41 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
enum {
// ML/FL / 1.0 and later:
OTP_SERIAL1 = 0,
OTP_HWVER = 1,
OTP_PCBA_SERIAL1 = 2,
// Quanta / HW 1.3 and later:
OTP_SERIAL2 = 3,
OTP_SERIAL3 = 4,
OTP_SERIAL4 = 5,
OTP_SERIAL5 = 6,
OTP_PCBA_SERIAL2 = 7,
OTP_PCBA_SERIAL3 = 8,
NUM_OTP_SLOTS = 16,
};
uint8_t * otp_get_lock(const uint8_t index);
bool otp_is_locked(const uint8_t index);
char * otp_get_slot(const uint8_t index);

View file

@ -0,0 +1,29 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "stm32f2xx.h"
typedef void (*ClockCmd)(uint32_t periph, FunctionalState state);
static inline void periph_config_enable(ClockCmd clock_cmd, uint32_t periph) {
clock_cmd(periph, ENABLE);
}
static inline void periph_config_disable(ClockCmd clock_cmd, uint32_t periph) {
clock_cmd(periph, DISABLE);
}

View file

@ -0,0 +1,30 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
//! Initialize the RTC with LSE as the clocksource
//! @return false if LSE init failed
bool rtc_init(void);
//! Set the RTC to run in fast mode
void rtc_initialize_fast_mode(void);
//! Slow down the RTC so we can keep time in standby mode
void rtc_slow_down(void);
//! Speed up the RTC for the firmware
void rtc_speed_up(void);

View file

@ -0,0 +1,93 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/button.h"
#include "board/board.h"
#include "drivers/periph_config.h"
#include "drivers/gpio.h"
static void initialize_button_common(void) {
if (!BOARD_CONFIG_BUTTON.button_com.gpio) {
// This board doesn't use a button common pin.
return;
}
// Configure BUTTON_COM to drive low. When the button
// is pressed this pin will be connected to the pin for the
// button.
gpio_use(BOARD_CONFIG_BUTTON.button_com.gpio);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = BOARD_CONFIG_BUTTON.button_com.gpio_pin;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(BOARD_CONFIG_BUTTON.button_com.gpio, &GPIO_InitStructure);
GPIO_WriteBit(BOARD_CONFIG_BUTTON.button_com.gpio, BOARD_CONFIG_BUTTON.button_com.gpio_pin, 0);
gpio_release(BOARD_CONFIG_BUTTON.button_com.gpio);
}
static void initialize_button(const ButtonConfig* config) {
// Configure the pin itself
gpio_use(config->gpio);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Pin = config->gpio_pin;
GPIO_Init(config->gpio, &GPIO_InitStructure);
gpio_release(config->gpio);
}
bool button_is_pressed(ButtonId id) {
const ButtonConfig* button_config = &BOARD_CONFIG_BUTTON.buttons[id];
gpio_use(button_config->gpio);
uint8_t bit = GPIO_ReadInputDataBit(button_config->gpio, button_config->gpio_pin);
gpio_release(button_config->gpio);
return !bit;
}
uint8_t button_get_state_bits(void) {
uint8_t button_state = 0x00;
for (int i = 0; i < NUM_BUTTONS; ++i) {
button_state |= (button_is_pressed(i) ? 0x01 : 0x00) << i;
}
return button_state;
}
void button_init(void) {
// Need to disable button wakeup functionality
// or the buttons don't register input
PWR_WakeUpPinCmd(DISABLE);
periph_config_enable(RCC_APB2PeriphClockCmd, RCC_APB2Periph_SYSCFG);
initialize_button_common();
for (int i = 0; i < NUM_BUTTONS; ++i) {
initialize_button(&BOARD_CONFIG_BUTTON.buttons[i]);
}
periph_config_disable(RCC_APB2PeriphClockCmd, RCC_APB2Periph_SYSCFG);
}

View file

@ -0,0 +1,108 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/crc.h"
#include "drivers/flash.h"
#include "drivers/periph_config.h"
#include "stm32f2xx_crc.h"
#include "stm32f2xx_rcc.h"
#include <inttypes.h>
static void prv_enable_crc_clock(void) {
periph_config_enable(RCC_AHB1PeriphClockCmd, RCC_AHB1Periph_CRC);
}
static void prv_disable_crc_clock(void) {
periph_config_disable(RCC_AHB1PeriphClockCmd, RCC_AHB1Periph_CRC);
}
static void prv_calculate_incremental_start(void) {
prv_enable_crc_clock();
CRC_ResetDR();
}
static void prv_calculate_incremental_words(const uint32_t* data, unsigned int data_length) {
CRC_CalcBlockCRC((uint32_t*) data, data_length);
}
static uint32_t prv_calculate_incremental_remaining_bytes(const uint8_t* data,
unsigned int data_length) {
uint32_t crc_value;
if (data_length >= 4) {
const unsigned int num_words = data_length / 4;
prv_calculate_incremental_words((uint32_t*) data, num_words);
data += num_words * 4;
data_length -= num_words * 4;
}
if (data_length) {
uint32_t last_word = 0;
for (unsigned int i = 0; i < data_length; ++i) {
last_word = (last_word << 8) | data[i];
}
crc_value = CRC_CalcCRC(last_word);
} else {
crc_value = CRC_GetCRC();
}
return crc_value;
}
static void prv_calculate_incremental_stop(void) {
prv_disable_crc_clock();
}
uint32_t crc_calculate_bytes(const uint8_t* data, unsigned int data_length) {
prv_calculate_incremental_start();
// First calculate the CRC of the whole words, since the hardware works 4
// bytes at a time.
uint32_t* data_words = (uint32_t*) data;
const unsigned int num_words = data_length / 4;
prv_calculate_incremental_words(data_words, num_words);
const unsigned int num_remaining_bytes = data_length % 4;
const uint32_t res =
prv_calculate_incremental_remaining_bytes(data + (num_words * 4), num_remaining_bytes);
prv_calculate_incremental_stop();
return (res);
}
uint32_t crc_calculate_flash(uint32_t address, unsigned int num_bytes) {
prv_calculate_incremental_start();
const unsigned int chunk_size = 128;
uint8_t buffer[chunk_size];
while (num_bytes > chunk_size) {
flash_read_bytes(buffer, address, chunk_size);
prv_calculate_incremental_words((const uint32_t*) buffer, chunk_size / 4);
num_bytes -= chunk_size;
address += chunk_size;
}
flash_read_bytes(buffer, address, num_bytes);
const uint32_t res = prv_calculate_incremental_remaining_bytes(buffer, num_bytes);
prv_calculate_incremental_stop();
return (res);
}

View file

@ -0,0 +1,107 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/dbgserial.h"
#include "drivers/periph_config.h"
#include "drivers/gpio.h"
#include "stm32f2xx_rcc.h"
#include "stm32f2xx_gpio.h"
#include "stm32f2xx_usart.h"
#include "misc.h"
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
static const int SERIAL_BAUD_RATE = 230400;
void dbgserial_init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// Enable GPIO and UART3 peripheral clocks
gpio_use(GPIOC);
periph_config_enable(RCC_APB1PeriphClockCmd, RCC_APB1Periph_USART3);
// USART_OverSampling8Cmd(USART3, ENABLE);
/* Connect PXx to USARTx_Tx*/
GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_USART3);
/* Connect PXx to USARTx_Rx*/
GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_USART3);
/* Configure USART Tx as alternate function */
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* Configure USART Rx as alternate function */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* USART configuration */
USART_InitStructure.USART_BaudRate = SERIAL_BAUD_RATE;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART3, &USART_InitStructure);
/* Enable USART */
USART_Cmd(USART3, ENABLE);
gpio_release(GPIOC);
}
static void prv_putchar(uint8_t c) {
while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET) continue;
USART_SendData(USART3, c);
while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET) continue;
}
void dbgserial_print(const char* str) {
while (*str) {
prv_putchar(*str);
++str;
}
}
void dbgserial_newline(void) {
prv_putchar('\r');
prv_putchar('\n');
}
void dbgserial_putstr(const char* str) {
dbgserial_print(str);
dbgserial_newline();
}
void dbgserial_print_hex(uint32_t value) {
char buf[12];
itoa(value, buf, sizeof(buf));
dbgserial_print(buf);
}

View file

@ -0,0 +1,38 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/gpio.h"
#include <stdint.h>
#define MAX_GPIO (9)
static uint32_t s_gpio_clock_count[MAX_GPIO];
void gpio_use(GPIO_TypeDef* GPIOx) {
uint32_t idx = ((((uint32_t)GPIOx) - AHB1PERIPH_BASE) / 0x0400);
if ((idx < MAX_GPIO) && !(s_gpio_clock_count[idx]++)) {
SET_BIT(RCC->AHB1ENR, (0x1 << idx));
}
}
void gpio_release(GPIO_TypeDef* GPIOx) {
uint32_t idx = ((((uint32_t)GPIOx) - AHB1PERIPH_BASE) / 0x0400);
if ((idx < MAX_GPIO) && s_gpio_clock_count[idx] && !(--s_gpio_clock_count[idx])) {
CLEAR_BIT(RCC->AHB1ENR, (0x1 << idx));
}
}

View file

@ -0,0 +1,39 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/otp.h"
#include "stm32f2xx_flash.h"
#include <stdint.h>
#include <stdbool.h>
// See page 53 of STM Reference Manual RM0033:
#define OTP_SLOTS_BASE_ADDR (0x1FFF7800)
#define OTP_LOCKS_BASE_ADDR (0x1FFF7A00)
//! Each OTP slot is 32 bytes. There are 16 slots: [0-15]
char * otp_get_slot(const uint8_t index) {
return (char * const) (OTP_SLOTS_BASE_ADDR + (32 * index));
}
uint8_t * otp_get_lock(const uint8_t index) {
return (uint8_t * const) (OTP_LOCKS_BASE_ADDR + index);
}
bool otp_is_locked(const uint8_t index) {
return (*otp_get_lock(index) == 0);
}

View file

@ -0,0 +1,219 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdbool.h>
#include "drivers/dbgserial.h"
#include "drivers/periph_config.h"
#include "drivers/rtc.h"
#include "util/delay.h"
#include "system/rtc_registers.h"
#include "stm32f2xx_rcc.h"
#include "stm32f2xx_rtc.h"
//! LSE startup time, about 4 seconds empirically,
//! but we give it 30 seconds since it it fails we sadwatch
static const int LSE_READY_TIMEOUT_MS = 30000;
static const unsigned int LSE_FREQUENCY_HZ = 32768;
static const int RTC_ASYNC_PRESCALER = 7;
static const int RTC_SYNC_PRESCALER = 3;
static const unsigned int RTC_TICKS_HZ = 1024;
static const unsigned int TICKS_IN_INTERVAL = 60 * 60 * 24;
static uint32_t prv_get_asynchronous_prescaler(void) {
return (RTC->PRER >> 16) & 0x7f;
}
static uint32_t prv_get_synchronous_prescaler(void) {
return RTC->PRER & 0x1fff;
}
//! Are we in slow mode?
static bool prv_slow_mode() {
return prv_get_asynchronous_prescaler() == 0x7f && prv_get_synchronous_prescaler() == 0xff;
}
static bool prv_clocksource_is_lse_started(void) {
return RCC_GetFlagStatus(RCC_FLAG_LSERDY) != RESET;
}
static bool prv_clocksource_lse_configure(void) {
if (prv_clocksource_is_lse_started()) {
// LSE remains on through standby and resets so often don't need to do anything
return true;
}
dbgserial_putstr("Starting LSE oscillator");
RCC_LSEConfig(RCC_LSE_ON);
for (int i = 0; i < LSE_READY_TIMEOUT_MS; i++) {
if (prv_clocksource_is_lse_started()) {
return true;
}
delay_us(1000);
}
dbgserial_putstr("LSE oscillator did not start");
return false;
}
//! This routine relies on bootbits already having enabled
//! access to the PWR clock and backup domain. Re-enabling
//! it here breaks wakeup for some reason
//! Returns false if configuring LSE failed
bool rtc_init(void) {
if (!prv_clocksource_lse_configure()) {
return false;
}
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
RCC_RTCCLKCmd(ENABLE);
RTC_WaitForSynchro();
return true;
}
// Before entering standby we set the RTC to it's default time (Jan 1, 2000)
// here we calculate the seconds elapsed since then
static int32_t prv_seconds_since_standby(void) {
// This function assumes the RTC is running in slow mode
RTC_TimeTypeDef rtc_time;
RTC_GetTime(RTC_Format_BIN, &rtc_time);
RTC_DateTypeDef rtc_date;
RTC_GetDate(RTC_Format_BIN, &rtc_date);
// Unlike mktime there's no error checking here since if something goes wrong
// it'll just give us the wrong time anyway
unsigned days = rtc_date.RTC_Year * 365; // RTC_Year is 0-99
days += (rtc_date.RTC_Year / 4); // Leap years
// Cumulative days from previous months
const unsigned month_days[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
days += month_days[rtc_date.RTC_Month - 1 ]; // RTC_Month is 1-12
if ((rtc_date.RTC_Year + 1) % 4 == 0 && rtc_date.RTC_Month > 2) {
// On a leap year and past February so add a leap day.
days++;
}
// Add in previous days of the current month
days += rtc_date.RTC_Date - 1;
return rtc_time.RTC_Seconds + 60 * (rtc_time.RTC_Minutes + 60 *
(rtc_time.RTC_Hours + 24 * days));
}
void rtc_initialize_fast_mode(void) {
// We configure the RTC to run in "fast time". This means that the calendar will
// be completely wrong, as we're incrementing the second count many times for every
// real second. The firmware's driver will hide this fact from the rest of the
// system. The reason we're doing this is because the STM32F2 micro doesn't offer
// a subsecond field in their calendar, so we resort to crazy workarounds to get
// a higher resolution timer.
RTC_InitTypeDef rtc_init_struct;
RTC_StructInit(&rtc_init_struct);
_Static_assert((LSE_FREQUENCY_HZ / ((RTC_ASYNC_PRESCALER + 1) * (RTC_SYNC_PRESCALER + 1))) ==
RTC_TICKS_HZ, "Our prescalers won't create the clock we want");
_Static_assert(RTC_ASYNC_PRESCALER >= 6, "PREDIV_A < 6 - Coarse calibration will not work.");
rtc_init_struct.RTC_AsynchPrediv = RTC_ASYNC_PRESCALER;
rtc_init_struct.RTC_SynchPrediv = RTC_SYNC_PRESCALER;
RTC_Init(&rtc_init_struct);
// Reset RTC time to 0, fast mode doesn't use the date register so leave it alone
RTC_TimeTypeDef rtc_time;
RTC_TimeStructInit(&rtc_time);
RTC_SetTime(RTC_Format_BIN, &rtc_time);
}
void rtc_speed_up(void) {
if (!prv_slow_mode()) {
// If we're not in slow mode there's nothing to do
return;
}
// On standby the RTC is reset to date 0, so the RTC's time is really
// the number of seconds we've been in standby
int32_t elapsed_since_standby = prv_seconds_since_standby();
int32_t saved_time = RTC_ReadBackupRegister(CURRENT_TIME_REGISTER);
// Correct the saved time with the number of seconds we've been in standby mode
saved_time += elapsed_since_standby;
// Save time in the backup register so the firmware can read it once it boots
RTC_WriteBackupRegister(CURRENT_TIME_REGISTER, saved_time);
RTC_WriteBackupRegister(CURRENT_INTERVAL_TICKS_REGISTER, 0);
rtc_initialize_fast_mode();
}
static uint32_t prv_bcd_to_byte(uint32_t value) {
const uint32_t tmp = ((value & 0xF0) >> 0x4) * 10;
return (tmp + (value & 0x0F));
}
static uint32_t prv_cur_ticks(void) {
uint32_t time_register = RTC->TR;
const uint32_t hours = prv_bcd_to_byte((time_register & (RTC_TR_HT | RTC_TR_HU)) >> 16);
const uint32_t minutes = prv_bcd_to_byte((time_register & (RTC_TR_MNT | RTC_TR_MNU)) >> 8);
const uint32_t seconds = prv_bcd_to_byte(time_register & (RTC_TR_ST | RTC_TR_SU));
return (((hours * 60) + minutes) * 60) + seconds;
}
static uint32_t prv_elapsed_ticks(uint32_t before, uint32_t after) {
int32_t result = after - before;
if (result < 0) {
result = (TICKS_IN_INTERVAL - before) + after;
}
return result;
}
void rtc_slow_down(void) {
if (prv_slow_mode()) {
// If we're already slowed down there is nothing to do
return;
}
// Calculate the current time and then save it back into the backup register
int32_t last_save_time = RTC_ReadBackupRegister(CURRENT_TIME_REGISTER);
uint32_t last_save_ticks = RTC_ReadBackupRegister(CURRENT_INTERVAL_TICKS_REGISTER);
uint32_t ticks_since_save = prv_elapsed_ticks(last_save_ticks, prv_cur_ticks());
int32_t cur_time = last_save_time + ticks_since_save / RTC_TICKS_HZ;
// Save the current time into the backup registers
RTC_WriteBackupRegister(CURRENT_TIME_REGISTER, cur_time);
// Set the RTC back to defaults (normal prescalers)
RTC_InitTypeDef rtc_init_struct;
RTC_StructInit(&rtc_init_struct);
RTC_Init(&rtc_init_struct);
// Set the RTC to default date and time.
// When we speed up the clock we'll add the elapsed seconds
// to the saved register to get the correct time
RTC_TimeTypeDef rtc_default_time;
RTC_TimeStructInit(&rtc_default_time);
RTC_SetTime(RTC_Format_BIN, &rtc_default_time);
RTC_DateTypeDef rtc_default_date;
RTC_DateStructInit(&rtc_default_date);
RTC_SetDate(RTC_Format_BIN, &rtc_default_date);
}

View file

@ -0,0 +1,109 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/system_flash.h"
#include "drivers/dbgserial.h"
#include "util/misc.h"
#include "stm32f2xx_flash.h"
static uint16_t s_sectors[] = {
FLASH_Sector_0, FLASH_Sector_1, FLASH_Sector_2, FLASH_Sector_3,
FLASH_Sector_4, FLASH_Sector_5, FLASH_Sector_6, FLASH_Sector_7
};
static uint32_t s_sector_addresses[] = {
ADDR_FLASH_SECTOR_0, ADDR_FLASH_SECTOR_1, ADDR_FLASH_SECTOR_2, ADDR_FLASH_SECTOR_3,
ADDR_FLASH_SECTOR_4, ADDR_FLASH_SECTOR_5, ADDR_FLASH_SECTOR_6, ADDR_FLASH_SECTOR_7
};
int prv_get_sector_num_for_address(uint32_t address) {
if (address < s_sector_addresses[0]) {
dbgserial_print("address ");
dbgserial_print_hex(address);
dbgserial_putstr(" is outside system flash");
return -1;
}
for (size_t i=0; i < ARRAY_LENGTH(s_sector_addresses)-1; ++i) {
if (s_sector_addresses[i] <= address
&& address < s_sector_addresses[i+1]) {
return i;
}
}
return ARRAY_LENGTH(s_sector_addresses)-1;
}
bool system_flash_erase(
uint32_t address, size_t length,
SystemFlashProgressCb progress_callback, void *progress_context) {
if (length == 0) {
// Nothing to do
return true;
}
int first_sector = prv_get_sector_num_for_address(address);
int last_sector = prv_get_sector_num_for_address(address + length - 1);
if (first_sector < 0 || last_sector < 0) {
return false;
}
int count = last_sector - first_sector + 1;
if (progress_callback) {
progress_callback(0, count, progress_context);
}
FLASH_Unlock();
for (int sector = first_sector; sector <= last_sector; ++sector) {
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);
if (FLASH_EraseSector(
s_sectors[sector], VoltageRange_1) != FLASH_COMPLETE) {
dbgserial_print("failed to erase sector ");
dbgserial_print_hex(sector);
dbgserial_newline();
FLASH_Lock();
return false;
}
if (progress_callback) {
progress_callback(sector - first_sector + 1, count, progress_context);
}
}
FLASH_Lock();
return true;
}
bool system_flash_write(
uint32_t address, const void *data, size_t length,
SystemFlashProgressCb progress_callback, void *progress_context) {
FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
const uint8_t *data_array = data;
for (uint32_t i = 0; i < length; ++i) {
if (FLASH_ProgramByte(address + i, data_array[i]) != FLASH_COMPLETE) {
dbgserial_print("failed to write address ");
dbgserial_print_hex(address);
dbgserial_newline();
FLASH_Lock();
return false;
}
if (progress_callback && i % 128 == 0) {
progress_callback(i/128, length/128, progress_context);
}
}
FLASH_Lock();
return true;
}

View file

@ -0,0 +1,41 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/watchdog.h"
#include "stm32f2xx_dbgmcu.h"
#include "stm32f2xx_iwdg.h"
#include "stm32f2xx_rcc.h"
void watchdog_init(void) {
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
IWDG_SetPrescaler(IWDG_Prescaler_64); // ~8 seconds
IWDG_SetReload(0xfff);
IWDG_WriteAccessCmd(IWDG_WriteAccess_Disable);
DBGMCU_APB1PeriphConfig(DBGMCU_IWDG_STOP, ENABLE);
}
void watchdog_start(void) {
IWDG_Enable();
IWDG_ReloadCounter();
}
bool watchdog_check_reset_flag(void) {
return RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET;
}

View file

@ -0,0 +1,60 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "stm32f2xx_flash.h"
#define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) /* Base @ of Sector 0, 16 Kbytes */
#define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) /* Base @ of Sector 1, 16 Kbytes */
#define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) /* Base @ of Sector 2, 16 Kbytes */
#define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) /* Base @ of Sector 3, 16 Kbytes */
#define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000) /* Base @ of Sector 4, 64 Kbytes */
#define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000) /* Base @ of Sector 5, 128 Kbytes */
#define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000) /* Base @ of Sector 6, 128 Kbytes */
#define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000) /* Base @ of Sector 7, 128 Kbytes */
typedef void (*SystemFlashProgressCb)(
uint32_t progress, uint32_t total, void *context);
// Erase the sectors of flash which lie within the given address range.
//
// If the address range overlaps even one single byte of a sector, the entire
// sector is erased.
//
// If progress_callback is not NULL, it is called at the beginning of the erase
// process and after each sector is erased. The rational number (progress/total)
// increases monotonically as the sector erasue procedure progresses.
//
// Returns true if successful, false if an error occurred.
bool system_flash_erase(
uint32_t address, size_t length,
SystemFlashProgressCb progress_callback, void *progress_context);
// Write data into flash. The flash must already be erased.
//
// If progress_callback is not NULL, it is called at the beginning of the
// writing process and periodically thereafter. The rational number
// (progress/total) increases monotonically as the data is written.
//
// Returns true if successful, false if an error occurred.
bool system_flash_write(
uint32_t address, const void *data, size_t length,
SystemFlashProgressCb progress_callback, void *progress_context);

View file

@ -0,0 +1,24 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
void watchdog_init(void);
void watchdog_start(void);
bool watchdog_check_reset_flag(void);

View file

@ -0,0 +1,31 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "stm32f2xx.h"
#define FW_OLDWORLD_OFFSET (0x10000)
#define FW_NEWWORLD_OFFSET (0x04000)
#define FW_WORLD_DIFFERENCE (FW_OLDWORLD_OFFSET - FW_NEWWORLD_OFFSET)
#define FIRMWARE_OLDWORLD_BASE (FLASH_BASE + FW_OLDWORLD_OFFSET)
#define FIRMWARE_NEWWORLD_BASE (FLASH_BASE + FW_NEWWORLD_OFFSET)
// Byte offset of NeWo in firmware
#define FW_IDENTIFIER_OFFSET (28)
bool firmware_is_new_world(void* base);

View file

@ -0,0 +1,25 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
// Scratch space for firmware images (normal and recovery).
// We assume this is 64k aligned...
#define FLASH_REGION_FIRMWARE_SCRATCH_BEGIN 0x0
#define FLASH_REGION_FIRMWARE_SCRATCH_END 0x80000 // 512k
#define FLASH_REGION_SAFE_FIRMWARE_BEGIN 0x200000
#define FLASH_REGION_SAFE_FIRMWARE_END 0x280000 // 512k

View file

@ -0,0 +1,264 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "fw_copy.h"
#include "drivers/crc.h"
#include "drivers/dbgserial.h"
#include "drivers/display.h"
#include "drivers/flash.h"
#include "drivers/system_flash.h"
#include "firmware.h"
#include "flash_region.h"
#include "system/bootbits.h"
#include "system/firmware_storage.h"
#include "system/reset.h"
#include "util/misc.h"
#include "util/delay.h"
#include <inttypes.h>
#include <stdint.h>
#define MAX_CHUNK_SIZE 65536
static bool prv_check_valid_firmware_crc(uint32_t flash_address, FirmwareDescription* desc) {
dbgserial_putstr("Checksumming firmware update");
uint32_t crc = crc_calculate_flash(flash_address, desc->firmware_length);
return crc == desc->checksum;
}
// Fills in the first 50% of the progress bar
static void prv_display_erase_progress(
uint32_t progress, uint32_t total, void *ctx) {
display_firmware_update_progress(progress, total * 2);
}
//! Returns true if we're going to install a new world firmware
static bool prv_check_firmware_world(uint32_t flash_new_fw_addr) {
// Read the beginning of the firmware off flash to see if it's new world or old world
const unsigned world_length = FW_IDENTIFIER_OFFSET + sizeof(uint32_t); // NeWo read as uint32_t
uint8_t buffer[world_length];
flash_read_bytes(buffer, flash_new_fw_addr, world_length);
return firmware_is_new_world(buffer);
}
static bool prv_erase_old_firmware(bool new_world, uint32_t firmware_length) {
dbgserial_putstr("erase_old_firmware");
uint32_t system_flash_base = FIRMWARE_NEWWORLD_BASE;
uint32_t erase_length = firmware_length;
if (!new_world) {
// If the incoming firmware is an old world firmware, we need to not only erase
// enough room for the old world firmware but we also need to erase the area
// between the new world and old world base addresses. If we don't erase this
// area and just load in the old world firmware at FIRMWARE_OLDWORLD_BASE, the
// bootloader will still see the start of the old new world firmware in this
// section and will attempt to boot that.
dbgserial_putstr("Old World firmware base");
erase_length += FW_WORLD_DIFFERENCE;
}
return system_flash_erase(system_flash_base, erase_length, prv_display_erase_progress, 0);
}
// Fills in the last 50% of the progress bar
static void prv_display_write_progress(
uint32_t progress, uint32_t total, void *ctx) {
display_firmware_update_progress(progress/2 + total/2, total);
}
static bool prv_write_new_firmware(bool new_world, uint32_t flash_new_fw_start,
uint32_t firmware_length) {
dbgserial_putstr("write_new_firmware");
uint32_t system_flash_base = new_world ? FIRMWARE_NEWWORLD_BASE : FIRMWARE_OLDWORLD_BASE;
// We can't just read the flash like memory, so we gotta lift everything ourselves.
// buffer is static so it goes in BSS, since stack is only 8192 bytes
static uint8_t buffer[MAX_CHUNK_SIZE];
uint32_t chunk_size;
for (uint32_t i = 0; i < firmware_length; i += chunk_size) {
chunk_size = MIN(MAX_CHUNK_SIZE, firmware_length - i);
flash_read_bytes(buffer, flash_new_fw_start + i, chunk_size);
if (!system_flash_write(system_flash_base + i, buffer, chunk_size, NULL, NULL)) {
dbgserial_putstr("We're dead");
return false;
}
prv_display_write_progress(i, firmware_length, NULL);
}
return true;
}
static bool prv_check_firmware_crc(FirmwareDescription* firmware_description) {
dbgserial_print("Checksumming ");
dbgserial_print_hex(firmware_description->firmware_length);
dbgserial_putstr(" bytes");
void *system_flash_base = (void*)FIRMWARE_OLDWORLD_BASE;
if (firmware_is_new_world(NULL)) {
dbgserial_putstr("New World firmware system_flash_base");
system_flash_base = (void*)FIRMWARE_NEWWORLD_BASE;
} else {
dbgserial_putstr("Old World firmware system_flash_base");
}
uint32_t calculated_crc = crc_calculate_bytes((const uint8_t*)system_flash_base,
firmware_description->firmware_length);
dbgserial_print("Checksum - wanted ");
dbgserial_print_hex(firmware_description->checksum);
dbgserial_print(" got ");
dbgserial_print_hex(calculated_crc);
dbgserial_newline();
return calculated_crc == firmware_description->checksum;
}
typedef enum UpdateFirmwareResult {
UPDATE_FW_SUCCESS = 0,
UPDATE_FW_ERROR_MICRO_FLASH_UNTOUCHED = 1,
UPDATE_FW_ERROR_MICRO_FLASH_MANGLED = 2
} UpdateFirmwareResult;
static UpdateFirmwareResult prv_update_fw(uint32_t flash_new_fw_addr) {
display_firmware_update_progress(0, 1);
boot_bit_set(BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS);
FirmwareDescription firmware_description =
firmware_storage_read_firmware_description(flash_new_fw_addr);
if (!firmware_storage_check_valid_firmware_description(&firmware_description)) {
dbgserial_print("Desclen ");
dbgserial_print_hex(firmware_description.description_length);
dbgserial_print("\nFirmlen ");
dbgserial_print_hex(firmware_description.firmware_length);
dbgserial_print("\nXsum ");
dbgserial_print_hex(firmware_description.checksum);
dbgserial_putstr("\nInvalid firmware description!");
return UPDATE_FW_ERROR_MICRO_FLASH_UNTOUCHED;
}
if (!prv_check_valid_firmware_crc(
flash_new_fw_addr + sizeof(FirmwareDescription), &firmware_description)) {
dbgserial_putstr("Invalid firmware CRC in SPI flash!");
return UPDATE_FW_ERROR_MICRO_FLASH_UNTOUCHED;
}
bool new_world = prv_check_firmware_world(flash_new_fw_addr + sizeof(FirmwareDescription));
prv_erase_old_firmware(new_world, firmware_description.firmware_length);
prv_write_new_firmware(new_world, flash_new_fw_addr + sizeof(FirmwareDescription),
firmware_description.firmware_length);
if (!prv_check_firmware_crc(&firmware_description)) {
dbgserial_putstr(
"Our internal flash contents are bad (checksum failed)! "
"This is really bad!");
return UPDATE_FW_ERROR_MICRO_FLASH_MANGLED;
}
return UPDATE_FW_SUCCESS;
}
void check_update_fw(void) {
if (!boot_bit_test(BOOT_BIT_NEW_FW_AVAILABLE)) {
return;
}
if (boot_bit_test(BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS)) {
dbgserial_putstr("Our previous firmware update failed, aborting update.");
// Pretend like the new firmware bit wasn't set afterall. We'll just run the
// previous code, whether that was normal firmware or the recovery firmware.
boot_bit_clear(BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS);
boot_bit_clear(BOOT_BIT_NEW_FW_AVAILABLE);
boot_bit_clear(BOOT_BIT_NEW_FW_INSTALLED);
return;
}
dbgserial_putstr("New firmware is available!");
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO);
UpdateFirmwareResult result = prv_update_fw(FLASH_REGION_FIRMWARE_SCRATCH_BEGIN);
switch (result) {
case UPDATE_FW_SUCCESS:
break;
case UPDATE_FW_ERROR_MICRO_FLASH_UNTOUCHED:
// Our firmware update failed in a way that didn't break our previous
// firmware. Just run the previous code, whether that was normal firmware
// or the recovery firmware.
break;
case UPDATE_FW_ERROR_MICRO_FLASH_MANGLED:
// We've broken our internal flash when trying to update our normal
// firmware. Fall back immediately to the recovery firmare.
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
system_reset();
}
// Done, we're ready to boot.
boot_bit_clear(BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS);
boot_bit_clear(BOOT_BIT_NEW_FW_AVAILABLE);
boot_bit_set(BOOT_BIT_NEW_FW_INSTALLED);
}
bool switch_to_recovery_fw() {
dbgserial_putstr("Loading recovery firmware");
UpdateFirmwareResult result = prv_update_fw(FLASH_REGION_SAFE_FIRMWARE_BEGIN);
bool recovery_fw_ok = true;
switch (result) {
case UPDATE_FW_SUCCESS:
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO);
boot_bit_set(BOOT_BIT_RECOVERY_START_IN_PROGRESS);
break;
case UPDATE_FW_ERROR_MICRO_FLASH_UNTOUCHED:
case UPDATE_FW_ERROR_MICRO_FLASH_MANGLED:
// Keep us booting into recovery firmware.
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
if (!boot_bit_test(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE)) {
dbgserial_putstr("Failed to load recovery firmware, strike one. Try again.");
boot_bit_set(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE);
boot_bit_set(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED);
system_reset();
} else if (!boot_bit_test(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO)) {
dbgserial_putstr("Failed to load recovery firmware, strike two. Try again.");
boot_bit_set(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO);
boot_bit_set(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED);
system_reset();
} else {
dbgserial_putstr("Failed to load recovery firmware, strike three. SAD WATCH");
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO);
recovery_fw_ok = false;
}
break;
}
boot_bit_clear(BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS);
return recovery_fw_ok;
}

View file

@ -0,0 +1,24 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
void check_update_fw(void);
//! @return false if we've failed to load recovery mode too many times and we should just sadwatch
bool switch_to_recovery_fw(void);

View file

@ -0,0 +1,9 @@
#pragma once
#define GIT_TIMESTAMP @TIMESTAMP@
#define GIT_TAG "@TAG@"
#define GIT_MAJOR_VERSION @MAJOR_VERSION@
#define GIT_MINOR_VERSION @MINOR_VERSION@
#define GIT_PATCH_VERSION @PATCH_VERSION@
#define GIT_REVISION "@COMMIT@"

View file

@ -0,0 +1,39 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/dbgserial.h"
#include "system/reset.h"
static void prv_hard_fault_handler_c(unsigned int *hardfault_args) {
dbgserial_putstr("HARD FAULT");
#ifdef NO_WATCHDOG
__asm("bkpt\n");
while (1) continue;
#else
system_hard_reset();
#endif
}
void HardFault_Handler(void) {
// Grab the stack pointer, shove it into a register and call
// the c function above.
__asm("tst lr, #4\n"
"ite eq\n"
"mrseq r0, msp\n"
"mrsne r0, psp\n"
"b %0\n" :: "i" (prv_hard_fault_handler_c));
}

View file

@ -0,0 +1,523 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdint.h>
#include <inttypes.h>
#include "board/board.h"
#include "boot_tests.h"
#include "drivers/button.h"
#include "drivers/dbgserial.h"
#include "drivers/display.h"
#include "drivers/flash.h"
#include "drivers/periph_config.h"
#include "drivers/rtc.h"
#include "drivers/watchdog.h"
#include "firmware.h"
#include "fw_copy.h"
#include "pebble_errors.h"
#include "standby.h"
#include "system/bootbits.h"
#include "system/reset.h"
#include "util/delay.h"
#include "stm32f2xx.h"
static const uint8_t SELECT_BUTTON_MASK = 0x4;
bool firmware_is_new_world(void* base) {
uint32_t* fw_base = base;
if (fw_base == NULL) {
fw_base = (uint32_t*)FIRMWARE_NEWWORLD_BASE;
}
if (fw_base[0] == 0xFFFFFFFF ||
fw_base[1] == 0xFFFFFFFF) { // Erased flash
return false;
}
if (fw_base[FW_IDENTIFIER_OFFSET / sizeof(uint32_t)] != 0x4E65576F) { // NeWo
return false;
}
// TODO: Additional checks?
return true;
}
static void prv_get_fw_reset_vector(void **reset_handler,
void **initial_stack_pointer) {
void** fw_vector_table;
if (firmware_is_new_world(NULL)) {
fw_vector_table = (void**) FIRMWARE_NEWWORLD_BASE;
} else {
fw_vector_table = (void**) FIRMWARE_OLDWORLD_BASE;
}
*initial_stack_pointer = fw_vector_table[0];
*reset_handler = fw_vector_table[1];
}
static void prv_hw_reset(void) {
// Disable all interrupts, just in case.
for (int i = 0; i < 8; ++i) {
// Interrupt Clear-Enable Register
NVIC->ICER[i] = 0xFFFFFFFF;
// Interrupt Clear-Pending Register
NVIC->ICPR[i] = 0xFFFFFFFF;
}
// Set the peripheral clock enable registers to their reset values as
// specified in the reference manual.
RCC->AHB1ENR = 0;
RCC->AHB2ENR = 0;
RCC->AHB3ENR = 0;
RCC->APB1ENR = 0;
RCC->APB2ENR = 0;
// Reset most peripherals used by the bootloader. We want to minimize the
// chances that the firmware unintentionally relies on some state that the
// bootloader leaves behind. This includes disabling the PLL.
// GPIOs are not reset here: resetting them would change their output values,
// which could unintentionally modify peripherals (such as the display).
// The backup domain is not reset; that would be foolish.
RCC_DeInit();
// Reset flags for each bus taken from reset register lists in reference manual
// starting with 5.3.5 "RCC AHB1 peripheral reset register"
const uint32_t ahb1_periphs = 0
| RCC_AHB1Periph_CRC
| RCC_AHB1Periph_DMA1
| RCC_AHB1Periph_DMA2
| RCC_AHB1Periph_ETH_MAC
| RCC_AHB1Periph_OTG_HS;
RCC_AHB1PeriphResetCmd(ahb1_periphs, ENABLE);
RCC_AHB1PeriphResetCmd(ahb1_periphs, DISABLE);
const uint32_t ahb2_periphs = 0
| RCC_AHB2Periph_DCMI
| RCC_AHB2Periph_CRYP
| RCC_AHB2Periph_HASH
| RCC_AHB2Periph_RNG
| RCC_AHB2Periph_OTG_FS;
RCC_AHB2PeriphResetCmd(ahb2_periphs, ENABLE);
RCC_AHB2PeriphResetCmd(ahb2_periphs, DISABLE);
const uint32_t ahb3_periphs = 0
| RCC_AHB3Periph_FSMC;
RCC_AHB3PeriphResetCmd(ahb3_periphs, ENABLE);
RCC_AHB3PeriphResetCmd(ahb3_periphs, DISABLE);
const uint32_t apb1_periphs = 0
| RCC_APB1Periph_TIM2
| RCC_APB1Periph_TIM3
| RCC_APB1Periph_TIM4
| RCC_APB1Periph_TIM5
| RCC_APB1Periph_TIM6
| RCC_APB1Periph_TIM7
| RCC_APB1Periph_TIM12
| RCC_APB1Periph_TIM13
| RCC_APB1Periph_TIM14
| RCC_APB1Periph_WWDG
| RCC_APB1Periph_SPI2
| RCC_APB1Periph_SPI3
| RCC_APB1Periph_USART2
| RCC_APB1Periph_USART3
| RCC_APB1Periph_UART4
| RCC_APB1Periph_UART5
| RCC_APB1Periph_I2C1
| RCC_APB1Periph_I2C2
| RCC_APB1Periph_I2C3
| RCC_APB1Periph_CAN1
| RCC_APB1Periph_CAN2
| RCC_APB1Periph_PWR
| RCC_APB1Periph_DAC;
RCC_APB1PeriphResetCmd(apb1_periphs, ENABLE);
RCC_APB1PeriphResetCmd(apb1_periphs, DISABLE);
const uint32_t apb2_periphs = 0
| RCC_APB2Periph_TIM1
| RCC_APB2Periph_TIM8
| RCC_APB2Periph_USART1
| RCC_APB2Periph_USART6
| RCC_APB2Periph_ADC
| RCC_APB2Periph_SDIO
| RCC_APB2Periph_SPI1
| RCC_APB2Periph_SYSCFG
| RCC_APB2Periph_TIM9
| RCC_APB2Periph_TIM10
| RCC_APB2Periph_TIM11;
RCC_APB2PeriphResetCmd(apb2_periphs, ENABLE);
RCC_APB2PeriphResetCmd(apb2_periphs, DISABLE);
}
static void __attribute__((noreturn)) prv_jump_to_fw(void) {
void *initial_stack_pointer, *reset_handler;
prv_get_fw_reset_vector(&reset_handler, &initial_stack_pointer);
dbgserial_print("Booting firmware @ ");
dbgserial_print_hex((uintptr_t)reset_handler);
dbgserial_newline();
dbgserial_newline();
prv_hw_reset();
// The Cortex-M user guide states that the reset values for the core registers
// are as follows:
// R0-R12 = Unknown
// MSP = VECTOR_TABLE[0] (main stack pointer)
// PSP = Unknown (process stack pointer)
// LR = 0xFFFFFFFF
// PC = VECTOR_TABLE[1]
// PRIMASK = 0x0
// FAULTMASK = 0x0
// BASEPRI = 0x0
// CONTROL = 0x0
//
// Attempt to put the processor into as close to the reset state as possible
// before passing control to the firmware.
//
// No attempt is made to set CONTROL to zero as it should already be set to
// the reset value when this code executes.
__asm volatile (
"cpsie if\n" // Clear PRIMASK and FAULTMASK
"mov lr, 0xFFFFFFFF\n"
"mov sp, %[initial_sp]\n"
"bx %[reset_handler]\n"
: : [initial_sp] "r" (initial_stack_pointer),
[reset_handler] "r" (reset_handler)
);
__builtin_unreachable();
}
static bool prv_check_and_increment_reset_loop_detection_bits(void) {
uint8_t counter =
(boot_bit_test(BOOT_BIT_RESET_LOOP_DETECT_THREE) << 2) |
(boot_bit_test(BOOT_BIT_RESET_LOOP_DETECT_TWO) << 1) |
boot_bit_test(BOOT_BIT_RESET_LOOP_DETECT_ONE);
switch (++counter) {
case 1:
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_ONE);
break;
case 2:
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_ONE);
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_TWO);
break;
case 3:
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_ONE);
break;
case 4:
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_ONE);
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_TWO);
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_THREE);
break;
case 5:
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_ONE);
break;
case 6:
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_ONE);
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_TWO);
break;
case 7:
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_ONE);
break;
default:
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_ONE);
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_TWO);
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_THREE);
return true;
}
return false;
}
static bool prv_check_for_recovery_start_failure() {
return boot_bit_test(BOOT_BIT_RECOVERY_START_IN_PROGRESS);
}
static bool prv_check_for_fw_start_failure() {
// Add more failure conditions here.
if (!watchdog_check_reset_flag() && !boot_bit_test(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED)) {
// We're good, we're just starting normally.
dbgserial_putstr("Booting normally");
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
return false;
}
// We failed to start our firmware successfully!
if (watchdog_check_reset_flag()) {
dbgserial_putstr("Watchdog caused a reset");
}
if (boot_bit_test(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED)) {
dbgserial_putstr("Software failure caused a reset");
}
// Clean up after the last failure.
boot_bit_clear(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED);
// We have a "three strikes" algorithm: if the watch fails three times, return true
// to tell the parent we should load the recovery firmware. A reset for any other reason
// will reset this algorithm.
if (boot_bit_test(BOOT_BIT_FW_START_FAIL_STRIKE_TWO)) {
// Yikes, our firmware is screwed. Boot into recovery mode.
dbgserial_putstr("Boot failed, strike 3");
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
return true;
} else if (boot_bit_test(BOOT_BIT_FW_START_FAIL_STRIKE_ONE)) {
dbgserial_putstr("Boot failed, strike 2");
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
} else {
dbgserial_putstr("Boot failed, strike 1");
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
}
return false;
}
static bool prv_check_force_boot_recovery(void) {
if (boot_bit_test(BOOT_BIT_FORCE_PRF)) {
boot_bit_clear(BOOT_BIT_FORCE_PRF);
return true;
}
if (button_is_pressed(BUTTON_ID_UP) && button_is_pressed(BUTTON_ID_BACK)) {
dbgserial_putstr("Hold down UP + BACK for 5 secs. to force-boot PRF");
for (int i = 0; i < 5000; ++i) {
if (!(button_is_pressed(BUTTON_ID_UP) && button_is_pressed(BUTTON_ID_BACK))) {
// stop waiting if not held down any longer
return false;
}
delay_ms(1);
}
return true;
}
void *reset_vector, *initial_sp;
prv_get_fw_reset_vector(&reset_vector, &initial_sp);
if ((uintptr_t)reset_vector == 0xffffffff ||
(uintptr_t)initial_sp == 0xffffffff) {
dbgserial_putstr("Firmware is erased");
return true;
}
return false;
}
static void prv_sad_watch(uint32_t error_code) {
dbgserial_print("SAD WATCH: ");
dbgserial_print_hex(error_code);
dbgserial_newline();
display_error_code(error_code);
static uint8_t prev_button_state = 0;
prev_button_state = button_get_state_bits() & ~SELECT_BUTTON_MASK;
while (1) {
// See if we should restart
uint8_t button_state = button_get_state_bits() & ~SELECT_BUTTON_MASK;
if (button_state != prev_button_state) {
system_reset();
}
delay_ms(10);
}
}
static void prv_print_reset_reason(void) {
dbgserial_print("Reset Register ");
dbgserial_print_hex(RCC->CSR);
dbgserial_newline();
if (RCC_GetFlagStatus(RCC_FLAG_BORRST) == SET) {
dbgserial_putstr("Brown out reset");
}
}
// SystemInit does this for the firmware, but since the bootloader isn't using
// the vendor SystemInit, initialize the flash cache here
static void prv_configure_system_flash(void) {
// Enable flash instruction and data caches
FLASH->ACR = FLASH_ACR_ICEN | FLASH_ACR_DCEN;
}
// RTC and bootbit assume access to the backup
// registers has been enabled
static void prv_enable_backup_access() {
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
PWR_BackupAccessCmd(ENABLE); // Disable write-protect on RTC_BKP_x registers
}
void boot_main(void) {
prv_configure_system_flash();
prv_enable_backup_access();
dbgserial_init();
prv_print_reset_reason();
boot_bit_init();
if (!rtc_init()) {
// Need to initialize the display in this
// case so we can see the sad watch
display_init();
prv_sad_watch(ERROR_CANT_START_LSE);
}
// Standby checks need to know the button pressed state
button_init();
// On tintin the bootloader handles entering and leaving standby manually
if (boot_bit_test(BOOT_BIT_STANDBY_MODE_REQUESTED)) {
boot_bit_clear(BOOT_BIT_STANDBY_MODE_REQUESTED);
enter_standby_mode();
} else if (PWR_GetFlagStatus(PWR_FLAG_SB) == SET) { // Woke up from standby
// Clear the standby flag since only a power reset clears it
PWR_ClearFlag(PWR_FLAG_SB);
// Before coming out of standby make sure we should be waking up
if (should_leave_standby_mode()) {
leave_standby_mode();
} else {
dbgserial_putstr("returning to standby");
enter_standby_mode();
}
dbgserial_putstr("leaving standby");
} else {
// If not entering or leaving standby this is a cold boot. The firmware
// expects the clock to be running in fast mode
rtc_initialize_fast_mode();
}
// Print out our super cool bootloader logo:
// ______ __
// /_ __/ __/ /_
// / / /_ __/
// /_/ /_/
dbgserial_putstr(" ______ __\r\n/_ __/ __/ /\r\n / / /_ __/\r\n/_/ /_/\r\n");
boot_version_write();
// Write the bootloader version to serial-out
dbgserial_print("Bootloader version: ");
dbgserial_print_hex(boot_version_read());
dbgserial_newline();
if (boot_bit_test(BOOT_BIT_FW_STABLE)) {
dbgserial_putstr("Last firmware boot was stable; clear strikes");
boot_bit_clear(BOOT_BIT_FW_STABLE);
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO);
}
display_init();
display_boot_splash();
#ifdef DISPLAY_DEMO_LOOP
while (1) {
display_boot_splash();
delay_us(1000000);
for (int i=0; i <= 91; ++i) {
display_firmware_update_progress(i, 91);
delay_us(80000);
}
for (uint32_t i=0; i <= 0xf; ++i) {
display_error_code(i * 0x11111111);
delay_us(200000);
}
for (uint32_t i=0; i < 8; ++i) {
for (uint32_t j=1; j <= 0xf; ++j) {
display_error_code(j << (i*4));
delay_us(200000);
}
}
display_error_code(0x01234567);
delay_us(200000);
display_error_code(0x89abcdef);
delay_us(200000);
display_error_code(0xcafebabe);
delay_us(200000);
display_error_code(0xfeedface);
delay_us(200000);
display_error_code(0x8badf00d);
delay_us(200000);
display_error_code(0xbad1ce40);
delay_us(200000);
display_error_code(0xbeefcace);
delay_us(200000);
display_error_code(0x0defaced);
delay_us(200000);
display_error_code(0xd15ea5e5);
delay_us(200000);
display_error_code(0xdeadbeef);
delay_us(200000);
}
#endif
flash_init();
if (is_button_stuck()) {
prv_sad_watch(ERROR_STUCK_BUTTON);
}
if (is_flash_broken()) {
prv_sad_watch(ERROR_BAD_SPI_FLASH);
}
boot_bit_dump();
// If the recovery firmware crashed at start-up, the watch is now a
// $150 brick. That's life!
if (prv_check_for_recovery_start_failure()) {
boot_bit_clear(BOOT_BIT_RECOVERY_START_IN_PROGRESS);
prv_sad_watch(ERROR_CANT_LOAD_FW);
}
bool force_boot_recovery_mode = prv_check_force_boot_recovery();
if (force_boot_recovery_mode) {
dbgserial_putstr("Force-booting recovery mode...");
}
if (force_boot_recovery_mode || prv_check_for_fw_start_failure()) {
if (!switch_to_recovery_fw()) {
// We've failed to load recovery mode too many times.
prv_sad_watch(ERROR_CANT_LOAD_FW);
}
} else {
check_update_fw();
}
if (prv_check_and_increment_reset_loop_detection_bits()) {
prv_sad_watch(ERROR_RESET_LOOP);
}
watchdog_init();
#ifndef NO_WATCHDOG
watchdog_start();
#endif
prv_jump_to_fw();
}

View file

@ -0,0 +1,34 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
static const uint32_t ERROR_NO_ACTIVE_ERROR = 0;
// FW Errors
static const uint32_t ERROR_STUCK_BUTTON = 0xfe504501;
static const uint32_t ERROR_BAD_SPI_FLASH = 0xfe504502;
static const uint32_t ERROR_CANT_LOAD_FW = 0xfe504503;
static const uint32_t ERROR_RESET_LOOP = 0xfe504504;
// BT Errors
static const uint32_t ERROR_CANT_LOAD_BT = 0xfe504510;
static const uint32_t ERROR_CANT_START_LSE = 0xfe504511;
static const uint32_t ERROR_CANT_START_ACCEL = 0xfe504512;
static const uint32_t ERROR_LOW_BATTERY = 0xfe504520;

View file

@ -0,0 +1,186 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "board/board.h"
#include "drivers/button.h"
#include "drivers/otp.h"
#include "drivers/rtc.h"
#include "drivers/periph_config.h"
#include "drivers/dbgserial.h"
#include "system/reset.h"
static bool prv_is_wake_on_usb_supported(void) {
// we accidentally left off a pull-up on early BB2s and v1_5 boards
// with the upshot of not being able to support wake from standby on VUSB
if (BOARD_CONFIG_POWER.wake_on_usb_power) {
if (!otp_is_locked(OTP_HWVER)) {
dbgserial_putstr("No HW Version in OTP");
// let's be optimistic
return true;
}
const char* hw_ver = otp_get_slot(OTP_HWVER);
#if defined(BOARD_BB2)
// We fixed the issue for BB2.1 (900-0-22-02-R1)
const char* no_support_hw_ver = "BB2.0";
return (memcmp(hw_ver, no_support_hw_ver, strlen(no_support_hw_ver)) != 0);
#elif defined(BOARD_V1_5)
// We fixed the issue for V3R2 (101-0-22-10-R3)
const char* no_support_hw_ver = "V3R1";
return (memcmp(hw_ver, no_support_hw_ver, strlen(no_support_hw_ver)) != 0);
#else
return true;
#endif
} else {
return false;
}
}
static void prv_wait_until_buttons_are_released(void) {
for (int bounce_count = 0; bounce_count < 10; ++bounce_count) {
uint8_t button_state;
// First, see if the buttons are all released for a period of time.
for (int i = 0; i < 10000; ++i) {
button_state = button_get_state_bits();
if (button_state != 0) {
// Someone pushed a button!
break;
}
}
if (button_state == 0) {
// We made it through with all the buttons released. We're good.
return;
}
// Alright, so either the button is held down or we hit a bounce. Wait
// for all the buttons to release again.
// 100000 is about a second in practice
for (int i = 0; i < 100000; ++i) {
if (button_get_state_bits() == 0) {
// All the buttons are released!
break;
}
}
}
}
static void prv_clear_wakeup_flags(void) {
// This function follows the steps listed in Erratum 2.1.4 "Wakeup sequence from Standby mode..."
// to avoid a situation where the watch cannot wake up
// or immediately wakes up after going into standby.
// The erratum says all used wakeup sources need to be disabled before
// reenabling the required ones, so to be safe we disable all wakeup sources
// to avoid dependence on knowing which wakeup sources the firmware left set
// Possible wakeup sources taken from reference manual 4.3.5 "Exiting Standby Mode"
// Disable the Wakeup pin
PWR_WakeUpPinCmd(DISABLE);
// Clear RTC interrupts, this ensures the flags won't be reset after we clear them
RTC_ITConfig(RTC_IT_TAMP
| RTC_IT_TS
| RTC_IT_WUT
| RTC_IT_ALRA
| RTC_IT_ALRB, DISABLE);
// Clear all RTC wakeup flags
RTC_ClearFlag(RTC_FLAG_TAMP1F
| RTC_FLAG_TSF
| RTC_FLAG_WUTF
| RTC_FLAG_ALRBF
| RTC_FLAG_ALRAF);
// At this point we know the wakeup flags are cleared so we can clear the PWR wakeup flag
PWR->CR |= PWR_CR_CWUF;
}
static void prv_enable_wake_on_usb(void) {
// Use the RTC timestamp alternate function to trigger a wakeup from the VUSB interrupt
// We don't clear all the wakeup flags here as said in
// 4.3.6 "Safe RTC alternate function wakeup flag clearing sequence", because
// prv_clear_wakeup_flags already cleared them for use by multiple wakeup sources
RTC_TimeStampPinSelection(RTC_TimeStampPin_PC13);
RTC_TimeStampCmd(RTC_TimeStampEdge_Falling, ENABLE);
RTC_ITConfig(RTC_IT_TS, ENABLE);
}
void enter_standby_mode(void) {
rtc_slow_down();
// Set wakeup events for the board
// If the WKUP pin is high when we enable wakeup, an additional
// wakeup event is registered (4.4.2 "PWR power control/status register"),
// which will cause the board to wake up immediately after entering standby. Therefore we
// wait until the button is released (or too much time has passed).
// It is possible to work around needing this by enabling the WKUP pin before
// clearing the PWR WUF flag, but that risks running afoul of errata 2.1.4
prv_wait_until_buttons_are_released();
prv_clear_wakeup_flags();
PWR_WakeUpPinCmd(ENABLE);
if (prv_is_wake_on_usb_supported()) {
dbgserial_putstr("usb wakeup supported");
prv_enable_wake_on_usb();
}
// Put the board into standby mode. The standard peripheral library provides PWR_EnterSTANDBYMode
// to do this, but that function clears the WUF (wakeup) flag. According to errata 2.1.4 if the
// wakeup flag is cleared when any wakeup source is high, further wakeup events may be masked.
// This means if a button press or usb plugin was to occur in between enabling the wakeup events
// and clearing the flag, the watch wouldn't wake up.
dbgserial_putstr("Entering standby");
// Steps to enter standby follow 4.3.5 "Entering Standby mode" Table 11
// (except where they conflict with errata 2.1.4)
// Select STANDBY mode
PWR->CR |= PWR_CR_PDDS;
// Set SLEEPDEEP bit on the cortex system control register
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
// Wait for interrupt
__WFI();
}
bool should_leave_standby_mode(void) {
if (RTC_GetFlagStatus(RTC_FLAG_TSF)) {
// we were woken by the USB power being plugged in
dbgserial_putstr("USB wakeup");
return true;
}
// Make sure a button is held down before waking up
for (int i = 0; i < 100000; ++i) {
if (button_get_state_bits() == 0) {
// stop waiting if not held down any longer and go back to sleep
return false;
}
}
return true;
}
void leave_standby_mode(void) {
// Speed up the RTC so the firmware doesn't need to deal with it
rtc_speed_up();
}

View file

@ -0,0 +1,26 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
//! Setup wakeup sources and put board into standby mode
void enter_standby_mode(void);
//! Check to make sure we should be leaving standby mode
bool should_leave_standby_mode(void);
//! Return functionality to normal after standby mode
void leave_standby_mode(void);

View file

@ -0,0 +1,80 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//! Initial firmware startup, contains the vector table that the bootloader loads.
//! Based on "https://github.com/pfalcon/cortex-uni-startup/blob/master/startup.c"
//! by Paul Sokolovsky (public domain)
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include "util/attributes.h"
//! These symbols are defined in the linker script for use in initializing
//! the data sections. uint8_t since we do arithmetic with section lengths.
//! These are arrays to avoid the need for an & when dealing with linker symbols.
extern uint8_t __data_load_start[];
extern uint8_t __data_start[];
extern uint8_t __data_end[];
extern uint8_t __bss_start[];
extern uint8_t __bss_end[];
extern uint8_t _estack[];
//! Bootloader entry point, ResetHandler calls this
extern int boot_main(void);
//! STM32 system initialization function, defined in the standard peripheral library
extern void SystemInit(void);
//! We don't use any interrupts in the bootloader so we map the core interrupts to
//! the HardFault_Handler to get useful debugging info if something goes wrong
extern void HardFault_Handler(void);
//! This function is what gets called when the processor first
//! starts execution following a reset event. The data and bss
//! sections are initialized, then we call the firmware's main
//! function
void Reset_Handler(void) {
// Copy data section from flash to RAM
memcpy(__data_start, __data_load_start, __data_end - __data_start);
// Clear the bss section, assumes .bss goes directly after .data
memset(__bss_start, 0, __bss_end - __bss_start);
boot_main();
// Main shouldn't return
while (true) {}
}
EXTERNALLY_VISIBLE SECTION(".isr_vector") void *vector_table[] = {
_estack,
Reset_Handler,
HardFault_Handler,
HardFault_Handler,
HardFault_Handler,
HardFault_Handler,
HardFault_Handler,
0,
0,
0,
0,
HardFault_Handler,
HardFault_Handler,
0,
HardFault_Handler,
HardFault_Handler
};

View file

@ -0,0 +1,236 @@
/*
Linker script for STM32F2xx_1024K_128K
*/
__Stack_Size = 8192;
/* Memory Spaces Definitions */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = @BOOTLOADER_LENGTH@
}
/* include the sections management sub-script for FLASH mode */
/* Sections Definitions */
SECTIONS
{
/* for Cortex devices, the beginning of the startup code is stored in the .isr_vector section, which goes to FLASH */
.isr_vector :
{
KEEP(*(.isr_vector)) /* Startup code */
} >FLASH
/* Exception handling sections. "contains index entries for section unwinding"
* We don't actually use this or know what it is, but it seems happy to go here. */
.ARM.exidx :
{
*(.ARM.exidx)
} >FLASH
/* Exported symbols:
* This list can be calculated by putting all the KEEP directives in the normal
* .text section, then using nm to find the addresses of each symbol. Then we
* put each exported symbol in its own section so we can control their addresses */
.text.memcpy 0x08000048 : { KEEP(*(.text.memcpy)) } > FLASH
.text.memset 0x0800005a : { KEEP(*(.text.memset)) } > FLASH
.text.memchr 0x0800006a : { KEEP(*(.text.memchr)) } > FLASH
.text.strcat 0x08000086 : { KEEP(*(.text.strcat)) } > FLASH
.text.strchr 0x0800009c : { KEEP(*(.text.strchr)) } > FLASH
.text.strcmp 0x080000b2 : { KEEP(*(.text.strcmp)) } > FLASH
.text.strcpy 0x080000c8 : { KEEP(*(.text.strcpy)) } > FLASH
.text.strlen 0x080000e0 : { KEEP(*(.text.strlen)) } > FLASH
.text.strnlen 0x080000ee : { KEEP(*(.text.strnlen)) } > FLASH
.text.strspn 0x08000100 : { KEEP(*(.text.strspn)) } > FLASH
.text.strtol 0x0800011c : { KEEP(*(.text.strtol)) } > FLASH
.text.atoi 0x08000140 : { KEEP(*(.text.atoi)) } > FLASH
.text.atol 0x08000168 : { KEEP(*(.text.atol)) } > FLASH
.text.puts 0x08000190 : { KEEP(*(.text.puts)) } > FLASH
.text.exit 0x08000194 : { KEEP(*(.text.exit)) } > FLASH
.text.strstr 0x08000196 : { KEEP(*(.text.strstr)) } > FLASH
.text.strcspn 0x080001c2 : { KEEP(*(.text.strcspn)) } > FLASH
.text.strncpy 0x080001e0 : { KEEP(*(.text.strncpy)) } > FLASH
.text.strncmp 0x08000214 : { KEEP(*(.text.strncmp)) } > FLASH
.text.strrchr 0x08000232 : { KEEP(*(.text.strrchr)) } > FLASH
.text.strncat 0x08000252 : { KEEP(*(.text.strncat)) } > FLASH
.text.memmove 0x0800027e : { KEEP(*(.text.memmove)) } > FLASH
.text.memcmp 0x080002a2 : { KEEP(*(.text.memcmp)) } > FLASH
.text.vsnprintf 0x080002bc : { KEEP(*(.text.vsnprintf)) } > FLASH
.text.snprintf 0x08000730 : { KEEP(*(.text.snprintf)) } > FLASH
.text.vsprintf 0x0800074a : { KEEP(*(.text.vsprintf)) } > FLASH
.text.sprintf 0x0800075c : { KEEP(*(.text.sprintf)) } > FLASH
.text.setlocale 0x08000776 : { KEEP(*(.text.setlocale)) } > FLASH
.text.RCC_ClearFlag 0x0800077c : { KEEP(*(.text.RCC_ClearFlag)) } > FLASH
.text.TIM_Cmd 0x0800078c : { KEEP(*(.text.TIM_Cmd)) } > FLASH
.text.CRC_ResetDR 0x08000810 : { KEEP(*(.text.CRC_ResetDR)) } > FLASH
.text.CRC_CalcCRC 0x0800081c : { KEEP(*(.text.CRC_CalcCRC)) } > FLASH
.text.CRC_CalcBlockCRC 0x08000828 : { KEEP(*(.text.CRC_CalcBlockCRC)) } > FLASH
.text.CRC_GetCRC 0x08000844 : { KEEP(*(.text.CRC_GetCRC)) } > FLASH
.text.DBGMCU_APB1PeriphConfig 0x08000850 : { KEEP(*(.text.DBGMCU_APB1PeriphConfig)) } > FLASH
.text.FLASH_Unlock 0x08000868 : { KEEP(*(.text.FLASH_Unlock)) } > FLASH
.text.FLASH_Lock 0x08000884 : { KEEP(*(.text.FLASH_Lock)) } > FLASH
.text.FLASH_ClearFlag 0x08000894 : { KEEP(*(.text.FLASH_ClearFlag)) } > FLASH
.text.FLASH_GetStatus 0x080008a0 : { KEEP(*(.text.FLASH_GetStatus)) } > FLASH
.text.FLASH_EraseSector 0x080008d4 : { KEEP(*(.text.FLASH_EraseSector)) } > FLASH
.text.FLASH_ProgramByte 0x08000944 : { KEEP(*(.text.FLASH_ProgramByte)) } > FLASH
.text.IWDG_WriteAccessCmd 0x0800097c : { KEEP(*(.text.IWDG_WriteAccessCmd)) } > FLASH
.text.IWDG_SetPrescaler 0x08000988 : { KEEP(*(.text.IWDG_SetPrescaler)) } > FLASH
.text.IWDG_SetReload 0x08000994 : { KEEP(*(.text.IWDG_SetReload)) } > FLASH
.text.IWDG_ReloadCounter 0x080009a0 : { KEEP(*(.text.IWDG_ReloadCounter)) } > FLASH
.text.IWDG_Enable 0x080009b0 : { KEEP(*(.text.IWDG_Enable)) } > FLASH
.text.PWR_BackupAccessCmd 0x080009c0 : { KEEP(*(.text.PWR_BackupAccessCmd)) } > FLASH
.text.PWR_WakeUpPinCmd 0x080009cc : { KEEP(*(.text.PWR_WakeUpPinCmd)) } > FLASH
.text.PWR_GetFlagStatus 0x080009d8 : { KEEP(*(.text.PWR_GetFlagStatus)) } > FLASH
.text.PWR_ClearFlag 0x080009ec : { KEEP(*(.text.PWR_ClearFlag)) } > FLASH
.text.RCC_DeInit 0x080009fc : { KEEP(*(.text.RCC_DeInit)) } > FLASH
.text.RCC_LSEConfig 0x08000a30 : { KEEP(*(.text.RCC_LSEConfig)) } > FLASH
.text.RCC_GetClocksFreq 0x08000a50 : { KEEP(*(.text.RCC_GetClocksFreq)) } > FLASH
.text.RCC_RTCCLKConfig 0x08000ad8 : { KEEP(*(.text.RCC_RTCCLKConfig)) } > FLASH
.text.RCC_RTCCLKCmd 0x08000b08 : { KEEP(*(.text.RCC_RTCCLKCmd)) } > FLASH
.text.RCC_AHB1PeriphClockCmd 0x08000b14 : { KEEP(*(.text.RCC_AHB1PeriphClockCmd)) } > FLASH
.text.RCC_APB1PeriphClockCmd 0x08000b2c : { KEEP(*(.text.RCC_APB1PeriphClockCmd)) } > FLASH
.text.RCC_APB2PeriphClockCmd 0x08000b44 : { KEEP(*(.text.RCC_APB2PeriphClockCmd)) } > FLASH
.text.RCC_AHB1PeriphResetCmd 0x08000b5c : { KEEP(*(.text.RCC_AHB1PeriphResetCmd)) } > FLASH
.text.RCC_AHB2PeriphResetCmd 0x08000b74 : { KEEP(*(.text.RCC_AHB2PeriphResetCmd)) } > FLASH
.text.RCC_AHB3PeriphResetCmd 0x08000b8c : { KEEP(*(.text.RCC_AHB3PeriphResetCmd)) } > FLASH
.text.RCC_APB1PeriphResetCmd 0x08000ba4 : { KEEP(*(.text.RCC_APB1PeriphResetCmd)) } > FLASH
.text.RCC_APB2PeriphResetCmd 0x08000bbc : { KEEP(*(.text.RCC_APB2PeriphResetCmd)) } > FLASH
.text.RCC_GetFlagStatus 0x08000bd4 : { KEEP(*(.text.RCC_GetFlagStatus)) } > FLASH
.text.RTC_EnterInitMode 0x08000bfc : { KEEP(*(.text.RTC_EnterInitMode)) } > FLASH
.text.RTC_ExitInitMode 0x08000c3c : { KEEP(*(.text.RTC_ExitInitMode)) } > FLASH
.text.RTC_Init 0x08000c4c : { KEEP(*(.text.RTC_Init)) } > FLASH
.text.RTC_WaitForSynchro 0x08000c90 : { KEEP(*(.text.RTC_WaitForSynchro)) } > FLASH
.text.RTC_SetTime 0x08000cd4 : { KEEP(*(.text.RTC_SetTime)) } > FLASH
.text.RTC_GetTime 0x08000d64 : { KEEP(*(.text.RTC_GetTime)) } > FLASH
.text.RTC_SetDate 0x08000da8 : { KEEP(*(.text.RTC_SetDate)) } > FLASH
.text.RTC_GetDate 0x08000e2c : { KEEP(*(.text.RTC_GetDate)) } > FLASH
.text.RTC_TimeStampCmd 0x08000e6c : { KEEP(*(.text.RTC_TimeStampCmd)) } > FLASH
.text.RTC_WriteBackupRegister 0x08000e94 : { KEEP(*(.text.RTC_WriteBackupRegister)) } > FLASH
.text.RTC_ReadBackupRegister 0x08000eb4 : { KEEP(*(.text.RTC_ReadBackupRegister)) } > FLASH
.text.RTC_TimeStampPinSelection 0x08000ed4 : { KEEP(*(.text.RTC_TimeStampPinSelection)) } > FLASH
.text.RTC_ITConfig 0x08000eec : { KEEP(*(.text.RTC_ITConfig)) } > FLASH
.text.RTC_GetFlagStatus 0x08000f28 : { KEEP(*(.text.RTC_GetFlagStatus)) } > FLASH
.text.RTC_ClearFlag 0x08000f44 : { KEEP(*(.text.RTC_ClearFlag)) } > FLASH
.text.SPI_I2S_DeInit 0x08000f60 : { KEEP(*(.text.SPI_I2S_DeInit)) } > FLASH
.text.USART_Init 0x08000fbc : { KEEP(*(.text.USART_Init)) } > FLASH
.text.FLASH_WaitForLastOperation 0x0800106c : { KEEP(*(.text.FLASH_WaitForLastOperation)) } > FLASH
.text.GPIO_Init 0x0800108e : { KEEP(*(.text.GPIO_Init)) } > FLASH
.text.GPIO_StructInit 0x0800110e : { KEEP(*(.text.GPIO_StructInit)) } > FLASH
.text.GPIO_ReadInputDataBit 0x08001120 : { KEEP(*(.text.GPIO_ReadInputDataBit)) } > FLASH
.text.GPIO_SetBits 0x0800112c : { KEEP(*(.text.GPIO_SetBits)) } > FLASH
.text.GPIO_ResetBits 0x08001130 : { KEEP(*(.text.GPIO_ResetBits)) } > FLASH
.text.GPIO_WriteBit 0x08001134 : { KEEP(*(.text.GPIO_WriteBit)) } > FLASH
.text.GPIO_PinAFConfig 0x0800113e : { KEEP(*(.text.GPIO_PinAFConfig)) } > FLASH
.text.RTC_ByteToBcd2 0x08001162 : { KEEP(*(.text.RTC_ByteToBcd2)) } > FLASH
.text.RTC_Bcd2ToByte 0x0800117c : { KEEP(*(.text.RTC_Bcd2ToByte)) } > FLASH
.text.RTC_StructInit 0x0800118e : { KEEP(*(.text.RTC_StructInit)) } > FLASH
.text.RTC_TimeStructInit 0x0800119a : { KEEP(*(.text.RTC_TimeStructInit)) } > FLASH
.text.RTC_DateStructInit 0x080011a6 : { KEEP(*(.text.RTC_DateStructInit)) } > FLASH
.text.SPI_Init 0x080011b4 : { KEEP(*(.text.SPI_Init)) } > FLASH
.text.SPI_Cmd 0x080011f2 : { KEEP(*(.text.SPI_Cmd)) } > FLASH
.text.SPI_I2S_ReceiveData 0x0800120a : { KEEP(*(.text.SPI_I2S_ReceiveData)) } > FLASH
.text.SPI_I2S_SendData 0x08001210 : { KEEP(*(.text.SPI_I2S_SendData)) } > FLASH
.text.SPI_I2S_GetFlagStatus 0x08001214 : { KEEP(*(.text.SPI_I2S_GetFlagStatus)) } > FLASH
.text.USART_Cmd 0x08001220 : { KEEP(*(.text.USART_Cmd)) } > FLASH
.text.USART_SendData 0x08001238 : { KEEP(*(.text.USART_SendData)) } > FLASH
.text.USART_GetFlagStatus 0x08001240 : { KEEP(*(.text.USART_GetFlagStatus)) } > FLASH
.text.ctype_data 0x0800124c : { KEEP(*(.rodata.__ctype_data)) } > FLASH
.text.bpapi 0x0800134c : { *(libgcc.a:bpapi.o*) } > FLASH
.text.uldivmod 0x08001378 : { *(libgcc.a:_aeabi_uldivmod.o*) } > FLASH
.text.dvmd 0x080013d8 : { *(libgcc.a:_dvmd_tls.o*) } > FLASH
.text.divdi3 0x080013dc : { *(libgcc.a:_divdi3.o*) } > FLASH
.text.udivdi3 0x0800167c : { *(libgcc.a:_udivdi3.o*) } > FLASH
/* the program code is stored in the .text section, which goes to Flash */
.text : {
*(.text) /* remaining code */
*(.text.*) /* remaining code */
*(.rodata) /* read-only data (constants) */
*(.rodata*)
*(.constdata) /* read-only data (constants) */
*(.constdata*)
. = ALIGN(4);
} >FLASH
/* This is the initialized data section
The program executes knowing that the data is in the RAM
but the loader puts the initial values in the FLASH (inidata).
It is one task of the startup to copy the initial values from FLASH to RAM. */
.data : {
. = ALIGN(4);
/* This is used by the startup in order to initialize the .data secion */
__data_start = .;
*(.data)
*(.data.*)
. = ALIGN(4);
__data_end = .; /* This is used by the startup in order to initialize the .data secion */
} >RAM AT>FLASH
__data_load_start = LOADADDR(.data);
/* This is the uninitialized data section */
.bss (NOLOAD) :
{
. = ALIGN(4);
__bss_start = .; /* This is used by the startup in order to initialize the .bss secion */
*(.bss)
*(.bss.*)
*(COMMON)
. = ALIGN(4);
__bss_end = .; /* This is used by the startup in order to initialize the .bss secion */
} >RAM
.stack (NOLOAD) :
{
. = ALIGN(8);
. += __Stack_Size;
_estack = .;
} >RAM
/* after that it's only debugging information. */
/* remove the debugging information from the standard libraries */
DISCARD : {
libgcc.a ( * )
}
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
}

View file

@ -0,0 +1,68 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/dbgserial.h"
#include "system/bootbits.h"
#include "system/rtc_registers.h"
#include "git_version.auto.h"
#include "stm32f2xx.h"
#include <inttypes.h>
#include <stdint.h>
static const uint32_t s_bootloader_timestamp = GIT_TIMESTAMP;
void boot_bit_init(void) {
if (!boot_bit_test(BOOT_BIT_INITIALIZED)) {
RTC_WriteBackupRegister(RTC_BKP_BOOTBIT_DR, BOOT_BIT_INITIALIZED);
}
}
void boot_bit_set(BootBitValue bit) {
uint32_t current_value = RTC_ReadBackupRegister(RTC_BKP_BOOTBIT_DR);
current_value |= bit;
RTC_WriteBackupRegister(RTC_BKP_BOOTBIT_DR, current_value);
}
void boot_bit_clear(BootBitValue bit) {
uint32_t current_value = RTC_ReadBackupRegister(RTC_BKP_BOOTBIT_DR);
current_value &= ~bit;
RTC_WriteBackupRegister(RTC_BKP_BOOTBIT_DR, current_value);
}
bool boot_bit_test(BootBitValue bit) {
uint32_t current_value = RTC_ReadBackupRegister(RTC_BKP_BOOTBIT_DR);
return (current_value & bit);
}
void boot_bit_dump(void) {
dbgserial_print("Boot bits: ");
dbgserial_print_hex(RTC_ReadBackupRegister(RTC_BKP_BOOTBIT_DR));
dbgserial_newline();
}
void boot_version_write(void) {
if (boot_version_read() == s_bootloader_timestamp) {
return;
}
RTC_WriteBackupRegister(BOOTLOADER_VERSION_REGISTER, s_bootloader_timestamp);
}
uint32_t boot_version_read(void) {
return RTC_ReadBackupRegister(BOOTLOADER_VERSION_REGISTER);
}

View file

@ -0,0 +1,53 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
typedef enum BootBitValue {
BOOT_BIT_INITIALIZED = 0x1 << 0,
BOOT_BIT_NEW_FW_AVAILABLE = 0x1 << 1,
BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS = 0x1 << 2,
BOOT_BIT_FW_START_FAIL_STRIKE_ONE = 0x1 << 3,
BOOT_BIT_FW_START_FAIL_STRIKE_TWO = 0x1 << 4,
BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE = 0x1 << 5,
BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO = 0x1 << 6,
BOOT_BIT_RECOVERY_START_IN_PROGRESS = 0x1 << 7,
BOOT_BIT_STANDBY_MODE_REQUESTED = 0x1 << 8,
BOOT_BIT_SOFTWARE_FAILURE_OCCURRED = 0x1 << 9,
BOOT_BIT_NEW_SYSTEM_RESOURCES_AVAILABLE = 0x1 << 10,
BOOT_BIT_RESET_LOOP_DETECT_ONE = 0x1 << 11,
BOOT_BIT_RESET_LOOP_DETECT_TWO = 0x1 << 12,
BOOT_BIT_RESET_LOOP_DETECT_THREE = 0x1 << 13,
BOOT_BIT_FW_STABLE = 0x1 << 14,
BOOT_BIT_NEW_FW_INSTALLED = 0x1 << 15,
BOOT_BIT_STANDBY_MODE_ENTERED = 0x1 << 16,
BOOT_BIT_FORCE_PRF = 0x1 << 17,
BOOT_BIT_NEW_PRF_AVAILABLE = 0x1 << 18
} BootBitValue;
void boot_bit_init();
void boot_bit_set(BootBitValue bit);
void boot_bit_clear(BootBitValue bit);
bool boot_bit_test(BootBitValue bit);
// Dump the contents through dbgserial
void boot_bit_dump(void);
void boot_version_write(void);
uint32_t boot_version_read(void);

View file

@ -0,0 +1,30 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "firmware_storage.h"
#include "drivers/flash.h"
FirmwareDescription firmware_storage_read_firmware_description(uint32_t firmware_start_address) {
FirmwareDescription firmware_description;
flash_read_bytes((uint8_t*)&firmware_description, firmware_start_address,
sizeof(FirmwareDescription));
return firmware_description;
}
bool firmware_storage_check_valid_firmware_description(FirmwareDescription* desc) {
return desc->description_length == sizeof(FirmwareDescription);
}

View file

@ -0,0 +1,33 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
//! @file firmware_storage.h
//! Utilities for reading a firmware image stored in flash.
#include <stdbool.h>
#include <stdint.h>
typedef struct __attribute__((__packed__)) FirmwareDescription {
uint32_t description_length;
uint32_t firmware_length;
uint32_t checksum;
} FirmwareDescription;
FirmwareDescription firmware_storage_read_firmware_description(uint32_t firmware_start_address);
bool firmware_storage_check_valid_firmware_description(FirmwareDescription* desc);

View file

@ -0,0 +1,36 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "system/reset.h"
#include "drivers/display.h"
#include "stm32f2xx.h"
void system_reset(void) {
display_prepare_for_reset();
// Clear the reset reason since it will no longer
// apply after this bootloader reset
RCC_ClearFlag();
system_hard_reset();
}
void system_hard_reset(void) {
NVIC_SystemReset();
__builtin_unreachable();
}

View file

@ -0,0 +1,26 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
//! Reset nicely after shutting down system services. Does not set the reboot_reason other than
//! calling reboot_reason_set_restarted_safely just before the reset occurs.
void system_reset(void)__attribute__((noreturn));
//! The final stage in the reset process.
void system_hard_reset(void) __attribute__((noreturn));

View file

@ -0,0 +1,33 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#define RTC_BKP_BOOTBIT_DR RTC_BKP_DR0
#define STUCK_BUTTON_REGISTER RTC_BKP_DR1
#define BOOTLOADER_VERSION_REGISTER RTC_BKP_DR2
#define CURRENT_TIME_REGISTER RTC_BKP_DR3
#define CURRENT_INTERVAL_TICKS_REGISTER RTC_BKP_DR4
#define REBOOT_REASON_REGISTER_1 RTC_BKP_DR5
#define REBOOT_REASON_REGISTER_2 RTC_BKP_DR6
#define REBOOT_REASON_STUCK_TASK_PC RTC_BKP_DR7
#define REBOOT_REASON_STUCK_TASK_LR RTC_BKP_DR8
#define REBOOT_REASON_STUCK_TASK_CALLBACK RTC_BKP_DR9
#define REBOOT_REASON_MUTEX_LR RTC_BKP_DR10 // Now REBOOT_REASON_DROPPED_EVENT
#define REBOOT_REASON_MUTEX_PC RTC_BKP_DR11 // Deprecated
#define MAG_XY_CORRECTION_VALS RTC_BKP_DR17
#define MAG_Z_CORRECTION_VAL RTC_BKP_DR18
#define SLOT_OF_LAST_LAUNCHED_APP RTC_BKP_DR19

View file

@ -0,0 +1,50 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#if defined(__clang__)
#define GCC_ONLY(x)
#else
#define GCC_ONLY(x) x
#endif
// Function attributes
#define FORMAT_FUNC(TYPE, STR_IDX, FIRST) __attribute__((__format__(TYPE, STR_IDX, FIRST)))
#define FORMAT_PRINTF(STR_IDX, FIRST) FORMAT_FUNC(__printf__, STR_IDX, FIRST)
#define ALWAYS_INLINE __attribute__((__always_inline__)) inline
#define NOINLINE __attribute__((__noinline__))
#define NORETURN __attribute__((__noreturn__)) void
#define NAKED_FUNC __attribute__((__naked__))
#define OPTIMIZE_FUNC(LVL) __attribute__((__optimize__(LVL)))
#define CONST_FUNC __attribute__((__const__))
#define PURE_FUNC __attribute__((__pure__))
// Variable attributes
#define ATTR_CLEANUP(FUNC) __attribute__((__cleanup__(FUNC)))
// Structure attributes
#define PACKED __attribute__((__packed__))
// General attributes
#define USED __attribute__((__used__))
#define UNUSED __attribute__((__unused__))
#define WEAK __attribute__((__weak__))
#define ALIAS(sym) __attribute__((__weak__, __alias__(sym)))
#define SECTION(SEC) GCC_ONLY(__attribute__((__section__(SEC))))
#define EXTERNALLY_VISIBLE GCC_ONLY(__attribute__((__externally_visible__)))

View file

@ -0,0 +1,49 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "delay.h"
#include "stm32f2xx.h"
#include "util/attributes.h"
#include <inttypes.h>
// Calculated using the formula from the firmware's delay_init:
// ceil( NS_PER_US / ( clock_period * INSTRUCTIONS_PER_LOOP )
// where NS_PER_US = 1000 and INSTRUCTIONS_PER_LOOP = 3,
// and clock_period = 62.5
#define LOOPS_PER_US (6)
void NOINLINE delay_us(uint32_t us) {
uint32_t delay_loops = us * LOOPS_PER_US;
__asm volatile (
"spinloop: \n"
" subs %[delay_loops], #1 \n"
" bne spinloop \n"
: [delay_loops] "+r" (delay_loops) // read-write operand
:
: "cc"
);
}
void delay_ms(uint32_t millis) {
// delay_us(millis*1000) is not used because a long delay could easily
// overflow the veriable. Without the outer loop, a delay of even five
// seconds would overflow.
while (millis--) {
delay_us(1000);
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <inttypes.h>
//! Carefully timed spinloop that allows one to delay at a microsecond
//! granularity.
void delay_us(uint32_t us);
//! Waits for a certain amount of milliseconds by busy-waiting.
//!
//! @param millis The number of milliseconds to wait for
void delay_ms(uint32_t millis);

View file

@ -0,0 +1,46 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "misc.h"
#include "drivers/dbgserial.h"
#include <stdint.h>
void itoa(uint32_t num, char *buffer, int buffer_length) {
if (buffer_length < 11) {
dbgserial_putstr("itoa buffer too small");
return;
}
*buffer++ = '0';
*buffer++ = 'x';
for (int i = 7; i >= 0; --i) {
uint32_t digit = (num & (0xf << (i * 4))) >> (i * 4);
char c;
if (digit < 0xa) {
c = '0' + digit;
} else if (digit < 0x10) {
c = 'a' + (digit - 0xa);
} else {
c = ' ';
}
*buffer++ = c;
}
*buffer = '\0';
}

View file

@ -0,0 +1,34 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define MHZ_TO_HZ(hz) (((uint32_t)(hz)) * 1000000)
#define ARRAY_LENGTH(array) (sizeof((array))/sizeof((array)[0]))
//! Find the log base two of a number rounded up
int ceil_log_two(uint32_t n);
//! Convert num to a hex string and put in buffer
void itoa(uint32_t num, char *buffer, int buffer_length);