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,85 @@
/*
* 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_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);
}
static void initialize_button(const ButtonConfig* config) {
// Configure the pin itself
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 = config->pull;
GPIO_InitStructure.GPIO_Pin = config->gpio_pin;
GPIO_Init(config->gpio, &GPIO_InitStructure);
}
bool button_is_pressed(ButtonId id) {
const ButtonConfig* button_config = &BOARD_CONFIG_BUTTON.buttons[id];
uint8_t bit = GPIO_ReadInputDataBit(button_config->gpio, button_config->gpio_pin);
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(PWR_WakeUp_Pin1, DISABLE);
PWR_WakeUpPinCmd(PWR_WakeUp_Pin2, DISABLE);
PWR_WakeUpPinCmd(PWR_WakeUp_Pin3, 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,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,176 @@
/*
* 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 "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_usart.h"
#include "util/attributes.h"
#include "util/cobs.h"
#include "util/crc32.h"
#include "util/misc.h"
#include "util/misc.h"
#include "util/net.h"
#include <stdbool.h>
#define MAX_MESSAGE (256)
#define FRAME_DELIMITER '\x55'
#define PULSE_TRANSPORT_PUSH (0x5021)
#define PULSE_PROTOCOL_LOGGING (0x0003)
static const int SERIAL_BAUD_RATE = 1000000;
typedef struct PACKED PulseFrame {
net16 protocol;
unsigned char information[];
} PulseFrame;
typedef struct PACKED PushPacket {
net16 protocol;
net16 length;
unsigned char information[];
} PushPacket;
static const unsigned char s_message_header[] = {
// Message type: text
1,
// Source filename
'B', 'O', 'O', 'T', 'L', 'O', 'A', 'D', 'E', 'R', 0, 0, 0, 0, 0, 0,
// Log level and task
'*', '*',
// Timestamp
0, 0, 0, 0, 0, 0, 0, 0,
// Line number
0, 0,
};
static size_t s_message_length = 0;
static unsigned char s_message_buffer[MAX_MESSAGE];
void dbgserial_init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// Enable GPIO and UART3 peripheral clocks
periph_config_enable(RCC_APB2PeriphClockCmd, RCC_APB2Periph_USART1);
// USART_OverSampling8Cmd(USART3, ENABLE);
/* Connect PXx to USARTx_Tx*/
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
/* Connect PXx to USARTx_Rx*/
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_USART1);
/* 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_9;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure USART Rx as alternate function */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOB, &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(USART1, &USART_InitStructure);
/* Enable USART */
USART_Cmd(USART1, ENABLE);
}
static void prv_putchar(uint8_t c) {
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) continue;
USART_SendData(USART1, c);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) continue;
}
void dbgserial_print(const char* str) {
for (; *str && s_message_length < MAX_MESSAGE; ++str) {
if (*str == '\n') {
dbgserial_newline();
} else if (*str != '\r') {
s_message_buffer[s_message_length++] = *str;
}
}
}
void dbgserial_newline(void) {
uint32_t crc;
size_t raw_length = sizeof(PulseFrame) + sizeof(PushPacket) +
sizeof(s_message_header) + s_message_length + sizeof(crc);
unsigned char raw_packet[raw_length];
PulseFrame *frame = (PulseFrame *)raw_packet;
frame->protocol = hton16(PULSE_TRANSPORT_PUSH);
PushPacket *transport = (PushPacket *)frame->information;
transport->protocol = hton16(PULSE_PROTOCOL_LOGGING);
transport->length = hton16(sizeof(PushPacket) + sizeof(s_message_header) +
s_message_length);
unsigned char *app = transport->information;
memcpy(app, s_message_header, sizeof(s_message_header));
memcpy(&app[sizeof(s_message_header)], s_message_buffer,
s_message_length);
crc = crc32(CRC32_INIT, raw_packet, raw_length - sizeof(crc));
memcpy(&raw_packet[raw_length - sizeof(crc)], &crc, sizeof(crc));
unsigned char cooked_packet[MAX_SIZE_AFTER_COBS_ENCODING(raw_length)];
size_t cooked_length = cobs_encode(cooked_packet, raw_packet, raw_length);
prv_putchar(FRAME_DELIMITER);
for (size_t i = 0; i < cooked_length; ++i) {
if (cooked_packet[i] == FRAME_DELIMITER) {
prv_putchar('\0');
} else {
prv_putchar(cooked_packet[i]);
}
}
prv_putchar(FRAME_DELIMITER);
s_message_length = 0;
}
void dbgserial_putstr(const char* str) {
dbgserial_print(str);
dbgserial_newline();
}
void dbgserial_print_hex(uint32_t value) {
char buf[12];
itoa_hex(value, buf, sizeof(buf));
dbgserial_print(buf);
}

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,365 @@
/*
* 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 "stm32f4xx_dma.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_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 DISPLAY_SPI_CLOCK (RCC_APB1Periph_SPI2)
#define DISP_PIN_SCS (GPIO_Pin_9)
#define DISP_PINSOURCE_SCS (GPIO_PinSource9)
#define DISP_PIN_SCLK (GPIO_Pin_10)
#define DISP_PINSOURCE_SCKL (GPIO_PinSource10)
#define DISP_PIN_SI (GPIO_Pin_15)
#define DISP_PINSOURCE_SI (GPIO_PinSource15)
#define DISP_LCD_GPIO (GPIOA)
#define DISP_PIN_LCD (GPIO_Pin_0)
#define DISP_PINSOURCE_LCD (GPIO_PinSource0)
#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_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_SET);
// required setup time > 3us
delay_us(7);
}
static void prv_disable_chip_select(void) {
// 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);
}
//! 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
};
static void prv_display_start(void) {
// Setup alternate functions
GPIO_PinAFConfig(DISP_GPIO, DISP_PINSOURCE_SCKL, GPIO_AF_SPI2);
GPIO_PinAFConfig(DISP_GPIO, DISP_PINSOURCE_SI, GPIO_AF_SPI2);
// Init display GPIOs
GPIO_Init(DISP_GPIO, &s_disp_gpio_init);
// Init CS
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);
// Init LCD Control
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_LCD;
GPIO_Init(DISP_LCD_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);
// Hold LCD on
GPIO_WriteBit(DISP_LCD_GPIO, DISP_PIN_LCD, Bit_SET);
}
// 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(line_addr + 1));
for (int i = 0; i < DISP_LINE_BYTES; ++i) {
prv_display_write_byte(prv_reverse_bits(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,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.
*/
#pragma once
#include "board/board.h"
typedef enum {
ExtiTrigger_Rising,
ExtiTrigger_Falling,
ExtiTrigger_RisingFalling
} ExtiTrigger;
//! See section 12.2.5 "External interrupt/event line mapping" in the STM32F2 reference manual
typedef enum {
ExtiLineOther_RTCAlarm = 17,
ExtiLineOther_RTCWakeup = 22
} ExtiLineOther;
typedef void (*ExtiHandlerCallback)(void);
//! Configures the given EXTI and NVIC for the given configuration.
void exti_configure_pin(ExtiConfig cfg, ExtiTrigger trigger, ExtiHandlerCallback cb);
//! Configures the given EXTI and NVIC for the given configuration.
void exti_configure_other(ExtiLineOther exti_line, ExtiTrigger trigger);
static inline void exti_enable(ExtiConfig config);
static inline void exti_disable(ExtiConfig config);
void exti_enable_other(ExtiLineOther);
void exti_disable_other(ExtiLineOther);
#include "exti.inl.h"

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.
*/
//! @file exti.inl.h
//!
//! Helper functions intended to be inlined into the calling code.
static inline void exti_enable(ExtiConfig config) {
exti_enable_other(config.exti_line);
}
static inline void exti_disable(ExtiConfig config) {
exti_disable_other(config.exti_line);
}

View file

@ -0,0 +1,40 @@
/*
* 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);
//! Get the checksum of a region of flash
uint32_t flash_calculate_checksum(uint32_t flash_addr, uint32_t length);

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/flash.h"
#include "util/crc32.h"
#define CRC_CHUNK_SIZE 1024
uint32_t flash_calculate_checksum(uint32_t flash_addr, uint32_t num_bytes) {
uint8_t buffer[CRC_CHUNK_SIZE];
uint32_t crc = CRC32_INIT;
while (num_bytes > CRC_CHUNK_SIZE) {
flash_read_bytes(buffer, flash_addr, CRC_CHUNK_SIZE);
crc = crc32(crc, buffer, CRC_CHUNK_SIZE);
num_bytes -= CRC_CHUNK_SIZE;
flash_addr += CRC_CHUNK_SIZE;
}
flash_read_bytes(buffer, flash_addr, num_bytes);
return crc32(crc, buffer, num_bytes);
}

View file

@ -0,0 +1,209 @@
/*
* 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 "board/board.h"
#include "drivers/flash.h"
#include "drivers/gpio.h"
#include "drivers/periph_config.h"
#include "util/delay.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_qspi.h"
#define MX25U_FASTREAD_DUMMYCYCLES 4
typedef enum MX25UCommand {
// SPI/QSPI Commands
MX25UCommand_FastRead = 0x0B, // FAST_READ
MX25UCommand_QSPIEnable = 0x35, // QPI
MX25UCommand_ResetEnable = 0x66, // RSTEN
MX25UCommand_Reset = 0x99, // RST
// QSPI only commands
MX25UCommand_QSPI_ID = 0xAF, // QPIID
} MX25UCommand;
// Helpful Enums
typedef enum {
QSPIFlag_Retain = 0,
QSPIFlag_ClearTC = 1,
} QSPIFlag;
static void prv_enable_qspi_clock(void) {
periph_config_enable(RCC_AHB3PeriphClockCmd, RCC_AHB3Periph_QSPI);
}
static void prv_disable_qspi_clock(void) {
periph_config_disable(RCC_AHB3PeriphClockCmd, RCC_AHB3Periph_QSPI);
}
static void prv_set_num_data_bytes(uint32_t length) {
// From the docs: QSPI_DataLength: Number of data to be retrieved, value+1.
// so 0 is 1 byte, so we substract 1 from the length. -1 is read the entire flash length.
QSPI_SetDataLength(length - 1);
}
static void prv_wait_for_qspi_transfer_complete(QSPIFlag actions) {
while (QSPI_GetFlagStatus(QSPI_FLAG_TC) == RESET) { }
if (actions == QSPIFlag_ClearTC) {
QSPI_ClearFlag(QSPI_FLAG_TC);
}
}
static void prv_wait_for_qspi_not_busy(void) {
while (QSPI_GetFlagStatus(QSPI_FLAG_BUSY) != RESET) { }
}
static void prv_quad_enable() {
QSPI_ComConfig_InitTypeDef qspi_com_config;
QSPI_ComConfig_StructInit(&qspi_com_config);
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Write;
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_1Line;
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_QSPIEnable;
QSPI_ComConfig_Init(&qspi_com_config);
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
prv_wait_for_qspi_not_busy();
}
static void prv_flash_reset(void) {
QSPI_ComConfig_InitTypeDef qspi_com_config;
QSPI_ComConfig_StructInit(&qspi_com_config);
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Write;
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_ResetEnable;
QSPI_ComConfig_Init(&qspi_com_config);
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
QSPI_ComConfig_StructInit(&qspi_com_config);
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Write;
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_Reset;
QSPI_ComConfig_Init(&qspi_com_config);
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
delay_us(12000); // 12ms reset if busy with an erase!
// Return the flash to Quad SPI mode, all our commands are quad-spi and it'll just cause
// problems/bugs for someone if it comes back in single spi mode
prv_quad_enable();
}
static bool prv_flash_check_whoami(void) {
const unsigned int num_whoami_bytes = 3;
prv_set_num_data_bytes(num_whoami_bytes);
QSPI_ComConfig_InitTypeDef qspi_com_config;
QSPI_ComConfig_StructInit(&qspi_com_config);
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Read;
qspi_com_config.QSPI_ComConfig_DMode = QSPI_ComConfig_DMode_4Line;
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_QSPI_ID;
QSPI_ComConfig_Init(&qspi_com_config);
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
uint32_t read_whoami = 0;
for (unsigned int i = 0; i < num_whoami_bytes; ++i) {
read_whoami |= QSPI_ReceiveData8() << (8 * i);
}
prv_wait_for_qspi_not_busy();
const uint32_t expected_whoami = 0x3725c2;
if (read_whoami == expected_whoami) {
return true;
} else {
return false;
}
}
void flash_init(void) {
prv_enable_qspi_clock();
// init GPIOs
for (unsigned i = 0; i < QSpiPinCount; ++i) {
gpio_af_init(&BOARD_CONFIG_FLASH_PINS[i], GPIO_OType_PP, GPIO_Speed_100MHz, GPIO_PuPd_NOPULL);
}
// Init QSPI peripheral
QSPI_InitTypeDef qspi_config;
QSPI_StructInit(&qspi_config);
qspi_config.QSPI_SShift = QSPI_SShift_HalfCycleShift;
qspi_config.QSPI_Prescaler = 0x0;
qspi_config.QSPI_CKMode = QSPI_CKMode_Mode0;
qspi_config.QSPI_CSHTime = QSPI_CSHTime_1Cycle;
qspi_config.QSPI_FSize = 22; // 2^23 = 8MB. -> 23 - 1 = 22
qspi_config.QSPI_FSelect = QSPI_FSelect_1;
qspi_config.QSPI_DFlash = QSPI_DFlash_Disable;
QSPI_Init(&qspi_config);
QSPI_Cmd(ENABLE);
// Must call quad_enable first, all commands are QSPI
prv_quad_enable();
// Reset the flash to stop any program's or erase in progress from before reboot
prv_flash_reset();
prv_disable_qspi_clock();
}
bool flash_sanity_check(void) {
prv_enable_qspi_clock();
bool result = prv_flash_check_whoami();
prv_disable_qspi_clock();
return result;
}
void flash_read_bytes(uint8_t *buffer_ptr, uint32_t start_addr, uint32_t buffer_size) {
prv_enable_qspi_clock();
prv_set_num_data_bytes(buffer_size);
QSPI_ComConfig_InitTypeDef qspi_com_config;
QSPI_ComConfig_StructInit(&qspi_com_config);
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Read;
qspi_com_config.QSPI_ComConfig_DMode = QSPI_ComConfig_DMode_4Line;
qspi_com_config.QSPI_ComConfig_DummyCycles = MX25U_FASTREAD_DUMMYCYCLES;
qspi_com_config.QSPI_ComConfig_ADMode = QSPI_ComConfig_ADMode_4Line;
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
qspi_com_config.QSPI_ComConfig_ADSize = QSPI_ComConfig_ADSize_24bit;
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_FastRead;
QSPI_ComConfig_Init(&qspi_com_config);
QSPI_SetAddress(start_addr);
uint8_t *write_ptr = buffer_ptr;
for (unsigned i = 0; i < buffer_size; ++i) {
write_ptr[i] = QSPI_ReceiveData8();
}
QSPI_ClearFlag(QSPI_FLAG_TC);
prv_wait_for_qspi_not_busy();
prv_disable_qspi_clock();
}

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.
*/
#include "drivers/gpio.h"
#include <stdint.h>
#define GPIO_EN_MASK ((RCC_AHB1ENR_GPIOHEN << 1) - 1)
void gpio_enable_all(void) {
RCC->AHB1ENR |= GPIO_EN_MASK;
}
void gpio_disable_all(void) {
RCC->AHB1ENR &= ~GPIO_EN_MASK;
}
void gpio_af_init(const AfConfig *af_config, GPIOOType_TypeDef otype, GPIOSpeed_TypeDef speed,
GPIOPuPd_TypeDef pupd) {
GPIO_InitTypeDef init = {
.GPIO_Pin = af_config->gpio_pin,
.GPIO_Mode = GPIO_Mode_AF,
.GPIO_Speed = speed,
.GPIO_OType = otype,
.GPIO_PuPd = pupd
};
GPIO_PinAFConfig(af_config->gpio, af_config->gpio_pin_source, af_config->gpio_af);
GPIO_Init(af_config->gpio, &init);
}

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 <stdbool.h>
#include "stm32f4xx_gpio.h"
#include "board/board.h"
void gpio_enable_all(void);
void gpio_disable_all(void);
void gpio_af_init(const AfConfig *af_config, GPIOOType_TypeDef otype, GPIOSpeed_TypeDef speed,
GPIOPuPd_TypeDef pupd);

View file

@ -0,0 +1,743 @@
/*
* 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 "util/misc.h"
#include "drivers/i2c.h"
#include "drivers/periph_config.h"
#include "drivers/gpio.h"
#include "system/passert.h"
#include "system/logging.h"
#include "util/delay.h"
#include <inttypes.h>
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_i2c.h"
#define portBASE_TYPE int
#define pdFALSE 0
#define portEND_SWITCHING_ISR(expr) (void)(expr)
#define I2C_ERROR_TIMEOUT_MS (1000)
#define I2C_TIMEOUT_ATTEMPTS_MAX (2 * 1000 * 1000)
#define I2C_NORMAL_MODE_CLOCK_SPEED_MAX (100000)
#define I2C_NACK_COUNT_MAX (1000) // MFI NACKs while busy. We delay ~1ms between retries so this is approximately a 1s timeout
#define I2C_READ_WRITE_BIT (0x01)
typedef struct I2cTransfer {
uint8_t device_address;
bool read_not_write; //True for read, false for write
uint8_t register_address;
uint8_t size;
uint8_t idx;
uint8_t *data;
enum TransferState {
TRANSFER_STATE_WRITE_ADDRESS_TX,
TRANSFER_STATE_WRITE_REG_ADDRESS,
TRANSFER_STATE_REPEAT_START,
TRANSFER_STATE_WRITE_ADDRESS_RX,
TRANSFER_STATE_WAIT_FOR_DATA,
TRANSFER_STATE_READ_DATA,
TRANSFER_STATE_WRITE_DATA,
TRANSFER_STATE_END_WRITE,
TRANSFER_STATE_INVALID,
} state;
bool result;
uint16_t nack_count;
}I2cTransfer;
typedef struct I2cBus{
I2C_TypeDef *i2c;
uint8_t user_count;
I2cTransfer transfer;
volatile bool busy;
}I2cBus;
static I2cBus i2c_buses[BOARD_I2C_BUS_COUNT];
static uint32_t s_guard_events[] = {
I2C_EVENT_MASTER_MODE_SELECT,
I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,
I2C_EVENT_MASTER_BYTE_TRANSMITTED,
I2C_EVENT_MASTER_MODE_SELECT,
I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED,
I2C_EVENT_MASTER_BYTE_RECEIVED,
I2C_EVENT_MASTER_BYTE_TRANSMITTING,
I2C_EVENT_MASTER_BYTE_TRANSMITTED,
};
static bool s_initialized = false;
/*----------------SEMAPHORE/LOCKING FUNCTIONS--------------------------*/
static void bus_lock(I2cBus *bus) {
}
static void bus_unlock(I2cBus *bus) {
}
static bool semaphore_take(I2cBus *bus) {
return true;
}
static bool semaphore_wait(I2cBus *bus) {
bus->busy = true;
volatile uint32_t timeout_attempts = I2C_TIMEOUT_ATTEMPTS_MAX;
while ((timeout_attempts-- > 0) && (bus->busy));
bus->busy = false;
return (timeout_attempts != 0);
}
static void semaphore_give(I2cBus *bus) {
}
/*-------------------BUS/PIN CONFIG FUNCTIONS--------------------------*/
//! Configure bus power supply control pin as output
//! Lock bus and peripheral config access before configuring pins
void i2c_bus_rail_ctl_config(OutputConfig pin_config) {
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = pin_config.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(pin_config.gpio, &GPIO_InitStructure);
}
//! Configure bus pins for use by I2C peripheral
//! Lock bus and peripheral config access before configuring pins
static void bus_pin_cfg_i2c(AfConfig pin_config) {
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.GPIO_Pin = pin_config.gpio_pin;
gpio_init_struct.GPIO_Mode = GPIO_Mode_AF;
gpio_init_struct.GPIO_Speed = GPIO_Speed_50MHz;
gpio_init_struct.GPIO_OType = GPIO_OType_OD;
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(pin_config.gpio, &gpio_init_struct);
GPIO_PinAFConfig(pin_config.gpio, pin_config.gpio_pin_source, pin_config.gpio_af);
}
//! Configure bus pin as input
//! Lock bus and peripheral config access before use
static void bus_pin_cfg_input(AfConfig pin_config) {
// Configure pin as high impedance input
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.GPIO_Pin = pin_config.gpio_pin;
gpio_init_struct.GPIO_Mode = GPIO_Mode_IN;
gpio_init_struct.GPIO_Speed = GPIO_Speed_2MHz;
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(pin_config.gpio, &gpio_init_struct);
}
//! Configure bus pin as output
//! Lock bus and peripheral config access before use
static void bus_pin_cfg_output(AfConfig pin_config, bool pin_state) {
// Configure pin as output
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.GPIO_Pin = pin_config.gpio_pin;
gpio_init_struct.GPIO_Mode = GPIO_Mode_OUT;
gpio_init_struct.GPIO_Speed = GPIO_Speed_2MHz;
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(pin_config.gpio, &gpio_init_struct);
// Set bit high or low
GPIO_WriteBit(pin_config.gpio, pin_config.gpio_pin, (pin_state) ? Bit_SET : Bit_RESET);
}
//! Power down I2C bus power supply
//! Always lock bus and peripheral config access before use
static void bus_rail_power_down(uint8_t bus_idx) {
if (BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn == NULL) {
return;
}
BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn(false);
// Drain through pull-ups
bus_pin_cfg_output(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl, false);
bus_pin_cfg_output(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda, false);
}
//! Power up I2C bus power supply
//! Always lock bus and peripheral config access before use
static void bus_rail_power_up(uint8_t bus_idx) {
if (BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn == NULL) {
return;
}
// check that at least enough time has elapsed since the last turn-off
// TODO: is this necessary in bootloader?
static const uint32_t MIN_STOP_TIME_MS = 10;
delay_ms(MIN_STOP_TIME_MS);
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl);
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda);
BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn(true);
}
//! Initialize the I2C peripheral
//! Lock the bus and peripheral config access before initialization
static void bus_init(uint8_t bus_idx) {
// Initialize peripheral
I2C_InitTypeDef i2c_init_struct;
I2C_StructInit(&i2c_init_struct);
if (BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_speed > I2C_NORMAL_MODE_CLOCK_SPEED_MAX) { //Fast mode
i2c_init_struct.I2C_DutyCycle = BOARD_CONFIG.i2c_bus_configs[bus_idx].duty_cycle;
}
i2c_init_struct.I2C_ClockSpeed = BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_speed;
i2c_init_struct.I2C_Ack = I2C_Ack_Enable;
I2C_Init(i2c_buses[bus_idx].i2c, &i2c_init_struct);
I2C_Cmd(i2c_buses[bus_idx].i2c, ENABLE);
}
//! Configure the bus pins, enable the peripheral clock and initialize the I2C peripheral.
//! Always lock the bus and peripheral config access before enabling it
static void bus_enable(uint8_t bus_idx) {
// Don't power up rail if the bus is already in use (enable can be called to reset bus)
if (i2c_buses[bus_idx].user_count == 0) {
bus_rail_power_up(bus_idx);
}
bus_pin_cfg_i2c(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl);
bus_pin_cfg_i2c(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda);
// Enable peripheral clock
periph_config_enable(RCC_APB1PeriphClockCmd, BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_ctrl);
bus_init(bus_idx);
}
//! De-initialize and gate the clock to the peripheral
//! Power down rail if the bus supports that and no devices are using it
//! Always lock the bus and peripheral config access before disabling it
static void bus_disable(uint8_t bus_idx) {
I2C_DeInit(i2c_buses[bus_idx].i2c);
periph_config_disable(RCC_APB1PeriphClockCmd, BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_ctrl);
// Do not de-power rail if there are still devices using bus (just reset peripheral and pin configuration during a bus reset)
if (i2c_buses[bus_idx].user_count == 0) {
bus_rail_power_down(bus_idx);
}
else {
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl);
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda);
}
}
//! Perform a soft reset of the bus
//! Always lock the bus before reset
static void bus_reset(uint8_t bus_idx) {
bus_disable(bus_idx);
bus_enable(bus_idx);
}
/*---------------INIT/USE/RELEASE/RESET FUNCTIONS----------------------*/
void i2c_init(void) {
for (uint32_t i = 0; i < ARRAY_LENGTH(i2c_buses); i++) {
i2c_buses[i].i2c = BOARD_CONFIG.i2c_bus_configs[i].i2c;
i2c_buses[i].user_count = 0;
i2c_buses[i].busy = false;
i2c_buses[i].transfer.idx = 0;
i2c_buses[i].transfer.size = 0;
i2c_buses[i].transfer.data = NULL;
i2c_buses[i].transfer.state = TRANSFER_STATE_INVALID;
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = BOARD_CONFIG.i2c_bus_configs[i].ev_irq_channel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0c;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = BOARD_CONFIG.i2c_bus_configs[i].er_irq_channel;
NVIC_Init(&NVIC_InitStructure);
I2C_DeInit(i2c_buses[i].i2c);
}
s_initialized = true;
for (uint32_t i = 0; i < ARRAY_LENGTH(i2c_buses); i++) {
if (BOARD_CONFIG.i2c_bus_configs[i].rail_cfg_fn) {
BOARD_CONFIG.i2c_bus_configs[i].rail_cfg_fn();
}
if (BOARD_CONFIG.i2c_bus_configs[i].rail_ctl_fn) {
bus_rail_power_down(i);
}
}
}
void i2c_use(I2cDevice device_id) {
PBL_ASSERTN(s_initialized);
PBL_ASSERT(device_id < BOARD_CONFIG.i2c_device_count, "I2C device ID out of bounds %d (max: %d)",
device_id, BOARD_CONFIG.i2c_device_count);
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
I2cBus *bus = &i2c_buses[bus_idx];
bus_lock(bus);
if (bus->user_count == 0) {
bus_enable(bus_idx);
}
bus->user_count++;
bus_unlock(bus);
}
void i2c_release(I2cDevice device_id) {
PBL_ASSERTN(s_initialized);
PBL_ASSERTN(device_id < BOARD_CONFIG.i2c_device_count);
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
I2cBus *bus = &i2c_buses[bus_idx];
bus_lock(bus);
if (bus->user_count == 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Attempted release of disabled bus %d by device %d", bus_idx, device_id);
bus_unlock(bus);
return;
}
bus->user_count--;
if (bus->user_count == 0) {
bus_disable(bus_idx);
}
bus_unlock(bus);
}
void i2c_reset(I2cDevice device_id) {
PBL_ASSERTN(s_initialized);
PBL_ASSERTN(device_id < BOARD_CONFIG.i2c_device_count);
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
I2cBus *bus = &i2c_buses[bus_idx];
// Take control of bus; only one task may use bus at a time
bus_lock(bus);
if (bus->user_count == 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Attempted reset of disabled bus %d by device %d", bus_idx, device_id);
bus_unlock(bus);
return;
}
PBL_LOG(LOG_LEVEL_WARNING, "Resetting I2C bus %" PRId8, bus_idx);
// decrement user count for reset so that if this user is the only user, the
// bus will be powered down during the reset
bus->user_count--;
// Reset and reconfigure bus and pins
bus_reset(bus_idx);
//Restore user count
bus->user_count++;
bus_unlock(bus);
}
/*--------------------DATA TRANSFER FUNCTIONS--------------------------*/
//! Wait a short amount of time for busy bit to clear
static bool wait_for_busy_clear(uint8_t bus_idx) {
unsigned int attempts = I2C_TIMEOUT_ATTEMPTS_MAX;
while((i2c_buses[bus_idx].i2c->SR2 & I2C_SR2_BUSY) != 0) {
--attempts;
if (!attempts) {
return false;
}
}
return true;
}
//! Abort the transfer
//! Should only be called when the bus is locked
static void abort_transfer(I2cBus *bus) {
// Disable all interrupts on the bus
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN | I2C_CR2_ITBUFEN);
// Generate a stop condition
bus->i2c->CR1 |= I2C_CR1_STOP;
bus->transfer.state = TRANSFER_STATE_INVALID;
}
//! Set up and start a transfer to a device, wait for it to finish and clean up after the transfer has completed
static bool do_transfer(I2cDevice device_id, bool read_not_write, uint8_t device_address, uint8_t register_address, uint8_t size, uint8_t *data) {
PBL_ASSERTN(s_initialized);
PBL_ASSERTN(device_id < BOARD_CONFIG.i2c_device_count);
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
I2cBus *bus = &i2c_buses[bus_idx];
// Take control of bus; only one task may use bus at a time
bus_lock(bus);
if (bus->user_count == 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Attempted access to disabled bus %d by device %d", bus_idx, device_id);
bus_unlock(bus);
return false;
}
// If bus is busy (it shouldn't be as this function waits for the bus to report a non-idle state
// before exiting) reset the bus and wait for it to become not-busy
// Exit if bus remains busy. User module should reset the I2C module at this point
if((bus->i2c->SR2 & I2C_SR2_BUSY) != 0) {
bus_reset(bus_idx);
if (!wait_for_busy_clear(bus_idx)) {
// Bus did not recover after reset
bus_unlock(bus);
return false;
}
}
// Take binary semaphore so that next take will block
PBL_ASSERT(semaphore_take(bus), "Could not acquire semaphore token");
// Set up transfer
bus->transfer.device_address = device_address;
bus->transfer.register_address = register_address;
bus->transfer.read_not_write = read_not_write;
bus->transfer.size = size;
bus->transfer.idx = 0;
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_TX;
bus->transfer.data = data;
bus->transfer.nack_count = 0;
// Ack received bytes
I2C_AcknowledgeConfig(bus->i2c, ENABLE);
bool result = false;
do {
// Generate start event
bus->i2c->CR1 |= I2C_CR1_START;
//Enable event and error interrupts
bus->i2c->CR2 |= I2C_CR2_ITEVTEN | I2C_CR2_ITERREN;
// Wait on semaphore until it is released by interrupt or a timeout occurs
if (semaphore_wait(bus)) {
if (bus->transfer.state == TRANSFER_STATE_INVALID) {
// Transfer is complete
result = bus->transfer.result;
if (!result) {
PBL_LOG(LOG_LEVEL_ERROR, "I2C Error on bus %" PRId8, bus_idx);
}
} else if (bus->transfer.nack_count < I2C_NACK_COUNT_MAX) {
// NACK received after start condition sent: the MFI chip NACKs start conditions whilst it is busy
// Retry start condition after a short delay.
// A NACK count is incremented for each NACK received, so that legitimate NACK
// errors cause the transfer to be aborted (after the NACK count max has been reached).
bus->transfer.nack_count++;
delay_ms(1);
} else {
// Too many NACKs received, abort transfer
abort_transfer(bus);
break;
PBL_LOG(LOG_LEVEL_ERROR, "I2C Error: too many NACKs received on bus %" PRId8, bus_idx);
}
} else {
// Timeout, abort transfer
abort_transfer(bus);
break;
PBL_LOG(LOG_LEVEL_ERROR, "Transfer timed out on bus %" PRId8, bus_idx);
}
} while (bus->transfer.state != TRANSFER_STATE_INVALID);
// Return semaphore token so another transfer can be started
semaphore_give(bus);
// Wait for bus to to clear the busy flag before a new transfer starts
// Theoretically a transfer could complete successfully, but the busy flag never clears,
// which would cause the next transfer to fail
if (!wait_for_busy_clear(bus_idx)) {
// Reset I2C bus if busy flag does not clear
bus_reset(bus_idx);
}
bus_unlock(bus);
return result;
}
bool i2c_read_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address, uint8_t *result) {
return i2c_read_register_block(device_id, i2c_device_address, register_address, 1, result);
}
bool i2c_read_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t
register_address_start, uint8_t read_size, uint8_t* result_buffer) {
// Do transfer locks the bus
bool result = do_transfer(device_id, true, i2c_device_address, register_address_start, read_size, result_buffer);
if (!result) {
PBL_LOG(LOG_LEVEL_ERROR, "Read failed on bus %" PRId8, BOARD_CONFIG.i2c_device_map[device_id]);
}
return result;
}
bool i2c_write_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address,
uint8_t value) {
return i2c_write_register_block(device_id, i2c_device_address, register_address, 1, &value);
}
bool i2c_write_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t
register_address_start, uint8_t write_size, const uint8_t* buffer) {
// Do transfer locks the bus
bool result = do_transfer(device_id, false, i2c_device_address, register_address_start, write_size, (uint8_t*)buffer);
if (!result) {
PBL_LOG(LOG_LEVEL_ERROR, "Write failed on bus %" PRId8, BOARD_CONFIG.i2c_device_map[device_id]);
}
return result;
}
/*------------------------INTERRUPT FUNCTIONS--------------------------*/
//! End a transfer and disable further interrupts
//! Only call from interrupt functions
static portBASE_TYPE end_transfer_irq(I2cBus *bus, bool result) {
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN | I2C_CR2_ITBUFEN);
bus->i2c->CR1 |= I2C_CR1_STOP;
bus->transfer.result = result;
bus->transfer.state = TRANSFER_STATE_INVALID;
bus->busy = false;
return pdFALSE;
}
//! Pause a transfer, disabling interrupts during the pause
//! Only call from interrupt functions
static portBASE_TYPE pause_transfer_irq(I2cBus *bus) {
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN | I2C_CR2_ITBUFEN);
bus->busy = false;
return pdFALSE;
}
//! Handle an IRQ event on the specified \a bus
static portBASE_TYPE irq_event_handler(I2cBus *bus) {
if (bus->transfer.state == TRANSFER_STATE_INVALID) {
// Disable interrupts if spurious interrupt received
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN);
return pdFALSE;
}
// Check that the expected event occurred
if (I2C_CheckEvent(bus->i2c, s_guard_events[bus->transfer.state]) == ERROR) {
// Ignore interrupt - A spurious byte transmitted event as well as an interrupt with no
// discernible event associated with it occur after repeat start events are generated
return pdFALSE;
}
portBASE_TYPE should_context_switch = pdFALSE;
switch (bus->transfer.state) {
case TRANSFER_STATE_WRITE_ADDRESS_TX:
// Write the i2c device address to the bus to select it in write mode.
bus->i2c->DR = bus->transfer.device_address & ~I2C_READ_WRITE_BIT;
bus->transfer.state = TRANSFER_STATE_WRITE_REG_ADDRESS;
break;
case TRANSFER_STATE_WRITE_REG_ADDRESS:
// Write the register address
bus->i2c->DR = bus->transfer.register_address;
if (bus->transfer.read_not_write) {
bus->transfer.state = TRANSFER_STATE_REPEAT_START;
} else {
// Enable TXE interrupt for writing
bus->i2c->CR2 |= I2C_CR2_ITBUFEN;
bus->transfer.state = TRANSFER_STATE_WRITE_DATA;
}
break;
case TRANSFER_STATE_REPEAT_START:
// Generate a repeat start
bus->i2c->CR1 |= I2C_CR1_START;
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_RX;
break;
case TRANSFER_STATE_WRITE_ADDRESS_RX:
// Write the I2C device address again, but this time in read mode.
bus->i2c->DR = bus->transfer.device_address | I2C_READ_WRITE_BIT;
if (bus->transfer.size == 1) {
// Last byte, we want to NACK this one to tell the slave to stop sending us bytes.
bus->i2c->CR1 &= ~I2C_CR1_ACK;
}
bus->transfer.state = TRANSFER_STATE_WAIT_FOR_DATA;
break;
case TRANSFER_STATE_WAIT_FOR_DATA:
//This state just ensures that the transition to receive mode event happened
// Enable RXNE interrupt for writing
bus->i2c->CR2 |= I2C_CR2_ITBUFEN;
bus->transfer.state = TRANSFER_STATE_READ_DATA;
break;
case TRANSFER_STATE_READ_DATA:
bus->transfer.data[bus->transfer.idx] = bus->i2c->DR;
bus->transfer.idx++;
if (bus->transfer.idx + 1 == bus->transfer.size) {
// Last byte, we want to NACK this one to tell the slave to stop sending us bytes.
bus->i2c->CR1 &= ~I2C_CR1_ACK;
}
else if (bus->transfer.idx == bus->transfer.size) {
// End transfer after all bytes have been received
bus->i2c->CR2 &= ~I2C_CR2_ITBUFEN;
should_context_switch = end_transfer_irq(bus, true);
break;
}
break;
case TRANSFER_STATE_WRITE_DATA:
bus->i2c->DR = bus->transfer.data[bus->transfer.idx];
bus->transfer.idx++;
if (bus->transfer.idx == bus->transfer.size) {
bus->i2c->CR2 &= ~I2C_CR2_ITBUFEN;
bus->transfer.state = TRANSFER_STATE_END_WRITE;
break;
}
break;
case TRANSFER_STATE_END_WRITE:
// End transfer after all bytes have been sent
should_context_switch = end_transfer_irq(bus, true);
break;
default:
// Abort transfer from invalid state - should never reach here (state machine logic broken)
should_context_switch = end_transfer_irq(bus, false);
break;
}
return should_context_switch;
}
//! Handle error interrupt on the specified \a bus
static portBASE_TYPE irq_error_handler(I2cBus *bus) {
if (bus->transfer.state == TRANSFER_STATE_INVALID) {
// Disable interrupts if spurious interrupt received
bus->i2c->CR2 &= ~I2C_CR2_ITERREN;
return pdFALSE;
}
// Data overrun and bus errors can only really be handled by terminating the transfer and
// trying to recover bus to an idle state. Each error will be logged. In each case a stop
// condition will be sent and then we will wait on the busy flag to clear (if it doesn't,
// a soft reset of the bus will be performed (handled in wait i2c_do_transfer).
if ((bus->i2c->SR1 & I2C_SR1_OVR) != 0) {
bus->i2c->SR1 &= ~I2C_SR1_OVR;
// Data overrun
PBL_LOG(LOG_LEVEL_ERROR, "Data overrun during I2C transaction; Bus: 0x%p", bus->i2c);
}
if ((bus->i2c->SR1 & I2C_SR1_BERR) != 0) {
bus->i2c->SR1 &= ~I2C_SR1_BERR;
// Bus error: invalid start or stop condition detected
PBL_LOG(LOG_LEVEL_ERROR, "Bus error detected during I2C transaction; Bus: 0x%p", bus->i2c);
}
if ((bus->i2c->SR1 & I2C_SR1_AF) != 0) {
bus->i2c->SR1 &= ~I2C_SR1_AF;
// NACK received.
//
// The MFI chip will cause NACK errors during read operations after writing a start bit (first start
// or repeat start indicating that it is busy. The transfer must be paused and the start condition sent
// again after a delay and the state machine set back a step.
//
// If the NACK is received after any other action log an error and abort the transfer
if (bus->transfer.state == TRANSFER_STATE_WAIT_FOR_DATA) {
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_RX;
return pause_transfer_irq(bus);
}
else if (bus->transfer.state == TRANSFER_STATE_WRITE_REG_ADDRESS){
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_TX;
return pause_transfer_irq(bus);
}
else {
PBL_LOG(LOG_LEVEL_ERROR, "NACK received during I2C transfer; Bus: 0x%p", bus->i2c);
}
}
return end_transfer_irq(bus, false);
}
void I2C1_EV_IRQHandler(void) {
portEND_SWITCHING_ISR(irq_event_handler(&i2c_buses[0]));
}
void I2C1_ER_IRQHandler(void) {
portEND_SWITCHING_ISR(irq_error_handler(&i2c_buses[0]));
}
void I2C2_EV_IRQHandler(void) {
portEND_SWITCHING_ISR(irq_event_handler(&i2c_buses[1]));
}
void I2C2_ER_IRQHandler(void) {
portEND_SWITCHING_ISR(irq_error_handler(&i2c_buses[1]));
}
void I2C3_EV_IRQHandler(void) {
portEND_SWITCHING_ISR(irq_event_handler(&i2c_buses[0]));
}
void I2C3_ER_IRQHandler(void) {
portEND_SWITCHING_ISR(irq_error_handler(&i2c_buses[0]));
}
/*------------------------COMMAND FUNCTIONS--------------------------*/
void command_power_2v5(char *arg) {
// Intentionally ignore the s_running_count and make it so!
// This is intended for low level electrical test only
if(!strcmp("on", arg)) {
bus_rail_power_up(1);
} else {
bus_rail_power_down(1);
}
}

View file

@ -0,0 +1,86 @@
/*
* 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 "board/board.h"
#include <stdbool.h>
#include <stdint.h>
//! Initialize the I2C driver. Must be called before first use
void i2c_init(void);
//! Start using the I2C bus connected to the device specified by \a device_id
//! Must be called before any other use of the bus is performed
//! @param device_id ID of device
void i2c_use(I2cDevice device_id);
//! Stop using the I2C bus connected to the device specified by \a device_id
//! Call when done with the bus
//! @param device_id ID of device
void i2c_release(I2cDevice device_id);
//! Reset the bus
//! Will re-initialize the bus and cycle the power to the bus if this is
//! supported for the bus the device specified by \a device_id is connected to)
//! @param device_id ID of device, this will identify the bus to be reset
void i2c_reset(I2cDevice device_id);
//! Manually bang out the clock on the bus specified by \a device_id for a period
//! of time or until the data line recovers
//! Must not be called before \ref i2c_use has been called for the device
//! @param device_id ID of device, this will identify the bus to be recovered
//! @return true if the data line recovered, false otherwise
bool i2c_bitbang_recovery(I2cDevice device_id);
//! Read the value of a register
//! Must not be called before \ref i2c_use has been called for the device
//! @param device_id ID of device to communicate with
//! @param i2c_device_address Device bus address
//! @param register_address Address of register to read
//! @param result Pointer to destination buffer
//! @return true if transfer succeeded, false if error occurred
bool i2c_read_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address, uint8_t *result);
//! Read a sequence of registers starting from \a register_address_start
//! Must not be called before \ref i2c_use has been called for the device
//! @param device_id ID of device to communicate with
//! @param i2c_device_address Device bus address
//! @param register_address_start Address of first register to read
//! @param read_size Number of bytes to read
//! @param result_buffer Pointer to destination buffer
//! @return true if transfer succeeded, false if error occurred
bool i2c_read_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address_start, uint8_t read_size, uint8_t* result_buffer);
//! Write to a register
//! Must not be called before \ref i2c_use has been called for the device
//! @param device_id ID of device to communicate with
//! @param i2c_device_address Device bus address
//! @param register_address Address of register to write to
//! @param value Data value to write
//! @return true if transfer succeeded, false if error occurred
bool i2c_write_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address, uint8_t value);
//! Write to a sequence of registers starting from \a register_address_start
//! Must not be called before \ref i2c_use has been called for the device
//! @param device_id ID of device to communicate with
//! @param i2c_device_address Device bus address
//! @param register_address_start Address of first register to read
//! @param write_size Number of bytes to write
//! @param buffer Pointer to source buffer
//! @return true if transfer succeeded, false if error occurred
bool i2c_write_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address_start, uint8_t write_size, const uint8_t* buffer);

View file

@ -0,0 +1,35 @@
/*
* 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/pmic.h"
#include "drivers/gpio.h"
#include "util/delay.h"
#include "board/board.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_i2c.h"
void silk_rail_ctl_fn(bool enable) {
// NYI
return;
}
void silk_rail_cfg_fn(void) {
// NYI
return;
}

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 "stm32f4xx.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,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>
//! Initialize the PMIC driver. Call this once at startup.
bool pmic_init(void);
//! Whether or not the usb charger is detected by the pmic.
bool pmic_is_usb_connected(void);
//! Returns true if this boot was caused by a charger event from standby, but
//! there is no charger connected.
bool pmic_boot_due_to_charger_disconnect(void);
//! Enter what we consider to be a "powered off" state, which is the as3701
//! standby state, where we keep the RTC powered.
bool pmic_power_off(void);
//! Fully shut down the as3701, which does not power the RTC.
bool pmic_full_power_off(void);

View file

@ -0,0 +1,311 @@
/*
* 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/pmic.h"
#include "board/board.h"
#include "drivers/gpio.h"
#include "drivers/i2c.h"
#include "system/passert.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_adc.h"
typedef enum {
PmicRegisters_SD1_VOLTAGE = 0x01,
PmicRegisters_LDO1_VOLTAGE = 0x02,
PmicRegisters_LDO2_VOLTAGE = 0x03,
PmicRegisters_GPIO1_CNTL = 0x09,
PmicRegisters_GPIO2_CNTL = 0x0a,
PmicRegisters_GPIO3_CNTL = 0x0b,
PmicRegisters_GPIO4_CNTL = 0x0c,
PmicRegisters_GPIO5_CNTL = 0x0d,
PmicRegisters_GPIO_SIG_OUT = 0x20,
PmicRegisters_GPIO_SIG_IN = 0x21,
PmicRegisters_REG1_VOLTAGE = 0x22,
PmicRegisters_REG2_VOLTAGE = 0x23,
PmicRegisters_REG_CNTL = 0x24,
PmicRegisters_GPIO_CNTL1 = 0x25,
PmicRegisters_GPIO_CNTL2 = 0x26,
PmicRegisters_SD_CNTL1 = 0x30,
PmicRegisters_BATT_VOLTAGE_MON = 0x32,
PmicRegisters_STARTUP_CNTL = 0x33,
PmicRegisters_REFERENCE_CNTL = 0x35,
PmicRegisters_RESET_CNTL = 0x36,
PmicRegisters_OVERTEMP_CNTL = 0x37,
PmicRegisters_REG_STANDBY_MOD1 = 0x39,
PmicRegisters_PWM_CNTL_L = 0x41,
PmicRegisters_PWM_CNTL_H = 0x42,
PmicRegisters_CURR1_VAL = 0x43,
PmicRegisters_CURR2_VAL = 0x44,
PmicRegisters_REG_STATUS = 0x73,
PmicRegisters_INT_MASK_1 = 0x74,
PmicRegisters_INT_MASK_2 = 0x75,
PmicRegisters_INT_STATUS_1 = 0x77,
PmicRegisters_INT_STATUS_2 = 0x78,
PmicRegisters_CHARGE_CNTL = 0x80,
PmicRegisters_CHARGE_VOLTAGE_CNTL = 0x81,
PmicRegisters_CHARGE_CURRENT_CNTL = 0x82,
PmicRegisters_CHARGE_CONFIG_1 = 0x83,
PmicRegisters_CHARGE_CONFIG_2 = 0x84,
PmicRegisters_CHARGE_SUPERVISION = 0x85,
PmicRegisters_CHARGE_STATUS_1 = 0x86,
PmicRegisters_CHARGE_STATUS_2 = 0x87,
PmicRegisters_LOCK_REG = 0x8e,
PmicRegisters_CHIP_ID = 0x90,
PmicRegisters_CHIP_REV = 0x91,
PmicRegisters_FUSE_5 = 0xa5,
PmicRegisters_FUSE_6 = 0xa6,
PmicRegisters_FUSE_7 = 0xa7,
PmicRegisters_FUSE_8 = 0xa8,
PmicRegisters_FUSE_9 = 0xa9,
PmicRegisters_FUSE_10 = 0xaa,
PmicRegisters_FUSE_11 = 0xab,
PmicRegisters_FUSE_12 = 0xac,
PmicRegisters_FUSE_13 = 0xad,
PmicRegisters_FUSE_14 = 0xae,
PmicRegisters_FUSE_15 = 0xaf,
} PmicRegisters;
// These are values for the reset_reason field of the ResetControl register.
// None of these values should ever be changed, as conversions are done on
// readings dont directly out of the ResetControl register.
// See Figure 79 of the AS3701B datasheet for more information.
typedef enum {
PmicResetReason_PowerUpFromScratch = 0x00, //!< Battery or charger insertion from scratch
PmicResetReason_ResVoltFall = 0x01, //!< Battery voltage drop below 2.75V
PmicResetReason_ForcedReset = 0x02, //!< sw force_reset
PmicResetReason_OnPulledHigh = 0x03, //!< Force sw power_off, ON pulled high
PmicResetReason_Charger = 0x04, //!< Forced sw power_off, Charger detected.
PmicResetReason_XRES = 0x05, //!< External trigger through XRES
PmicResetReason_OverTemperature = 0x06, //!< Reset caused by overtemperature
PmicResetReason_OnKeyHold = 0x08, //!< Reset for holding down on key
PmicResetReason_StandbyInterrupt = 0x0b, //!< Reset for interrupt in standby
PmicResetReason_StandbyOnPulledHigh = 0x0c, //!< Reset for ON pulled high in standby
PmicResetReason_Unknown,
} PmicResetReason;
static void prv_start_120hz_clock(void);
static bool prv_init_gpio(void) {
return true;
}
//! Interrupt masks for InterruptStatus1 and InterruptMask1 registers
enum PmicInt1 {
PmicInt1_Trickle = (1 << 0), //!< Trickle charge
PmicInt1_NoBat = (1 << 1), //!< Battery detached
PmicInt1_Resume = (1 << 2), //!< Resuming charge on drop after full
PmicInt1_EOC = (1 << 3), //!< End of charge
PmicInt1_ChDet = (1 << 4), //!< Charger detected
PmicInt1_OnKey = (1 << 5), //!< On Key held
PmicInt1_OvTemp = (1 << 6), //!< Set when 110deg is exceeded
PmicInt1_LowBat = (1 << 7), //!< Low Battery detected. Set when BSUP drops below ResVoltFall
};
enum PmicRail {
PmicRail_SD1, //!< 1.8V
PmicRail_LDO1, //!< 3.0V
PmicRail_LDO2, //!< 2.0V
};
#define AS3701B_CHIP_ID 0x11
#define AS3701B_WRITE_ADDR 0x80
#define AS3701B_READ_ADDR 0x81
static bool prv_read_register(uint8_t register_address, uint8_t *result) {
i2c_use(I2C_DEVICE_AS3701B);
bool rv = i2c_read_register(I2C_DEVICE_AS3701B, AS3701B_READ_ADDR, register_address, result);
i2c_release(I2C_DEVICE_AS3701B);
return rv;
}
static bool prv_write_register(uint8_t register_address, uint8_t value) {
i2c_use(I2C_DEVICE_AS3701B);
bool rv = i2c_write_register(I2C_DEVICE_AS3701B, AS3701B_WRITE_ADDR, register_address, value);
i2c_release(I2C_DEVICE_AS3701B);
return rv;
}
static bool prv_register_set_bit(uint8_t register_address, uint8_t bit) {
uint8_t val;
if (!prv_read_register(register_address, &val)) {
return false;
}
val |= (1 << bit);
return prv_write_register(register_address, val);
}
static bool prv_register_clear_bit(uint8_t register_address, uint8_t bit) {
uint8_t val;
if (!prv_read_register(register_address, &val)) {
return false;
}
val &= ~(1 << bit);
return prv_write_register(register_address, val);
}
// Read the interrupt status registers to clear pending bits.
static void prv_clear_pending_interrupts(void) {
uint8_t throwaway_read_result;
prv_read_register(PmicRegisters_INT_STATUS_1, &throwaway_read_result);
prv_read_register(PmicRegisters_INT_STATUS_2, &throwaway_read_result);
}
// Set up 120Hz clock which is used for VCOM.
// Slowest possible setting, with divisor of 16 and high/low duration of 256us.
void prv_start_120hz_clock(void) {
const uint8_t pwm_high_low_time_us = (256 - 1);
prv_write_register(PmicRegisters_PWM_CNTL_H, pwm_high_low_time_us);
prv_write_register(PmicRegisters_PWM_CNTL_L, pwm_high_low_time_us);
bool success = false;
uint8_t ref_cntl;
if (prv_read_register(PmicRegisters_REFERENCE_CNTL, &ref_cntl)) {
ref_cntl |= 0x3; // Divisor of 16
prv_write_register(PmicRegisters_REFERENCE_CNTL, ref_cntl);
// Enable PWM Output on GPIO2 (Fig. 64)
// Bits 6-4: Mode, 0x1 = Output
// Bits 0-3: iosf, 0xe = PWM
uint8_t val = (1 << 4) | 0x0e;
success = prv_write_register(PmicRegisters_GPIO2_CNTL, val);
}
PBL_ASSERT(success, "Failed to start PMIC 120Hz PWM");
}
static bool prv_is_alive(void) {
uint8_t chip_id;
if (!prv_read_register(PmicRegisters_CHIP_ID, &chip_id)) {
return false;
}
const bool found = (chip_id == AS3701B_CHIP_ID);
if (found) {
PBL_LOG(LOG_LEVEL_DEBUG, "Found the as3701b");
} else {
PBL_LOG(LOG_LEVEL_DEBUG, "Error: read as3701b whoami byte 0x%x, expecting 0x11", chip_id);
}
return found;
}
bool pmic_init(void) {
prv_init_gpio();
if (!prv_is_alive()) {
return false;
}
prv_start_120hz_clock();
return true;
}
bool pmic_is_usb_connected(void) {
uint8_t status;
if (!prv_read_register(PmicRegisters_CHARGE_STATUS_2, &status)) {
return false;
}
// ChargerStatus2 (Fig. 98)
// Bit 2: Charger detected
return !!(status & (1 << 2));
}
static PmicResetReason prv_reset_reason(void) {
uint8_t val;
if (!prv_read_register(PmicRegisters_RESET_CNTL, &val)) {
return PmicResetReason_Unknown;
}
return (val & 0xf0) >> 4;
}
// If the pmic indicates that we were reset due to a charger interrupt, but the
// charger is currently disconnected, then we know we were woken by a disconnect event.
bool pmic_boot_due_to_charger_disconnect(void) {
if (prv_reset_reason() != PmicResetReason_StandbyInterrupt) {
return false;
}
uint8_t int_status;
if (!prv_read_register(PmicRegisters_INT_STATUS_1, &int_status)) {
return false;
}
return ((int_status & PmicInt1_ChDet) && !pmic_is_usb_connected());
}
// This is a hard power off, resulting in all rails being disabled.
bool pmic_full_power_off(void) {
// ResetControl (Fig. 79)
// Bit 1: power_off - Start a reset cycle, and wait for ON or charger to complete the reset.
if (prv_register_set_bit(PmicRegisters_RESET_CNTL, 1)) {
while (1) {}
__builtin_unreachable();
}
return false;
}
// On the as3701b, a power_off will cut power to all rails. We want to keep the
// RTC alive, so rather than performing a sw_power_off, enter the pmic's standby
// mode, powering down all but LDO2.
bool pmic_power_off(void) {
// Only enable interrupts that should be able to wake us out of standby
// - Wake on charger detect
const uint8_t int_mask = ~((uint8_t)PmicInt1_ChDet);
prv_write_register(PmicRegisters_INT_MASK_1, int_mask);
prv_write_register(PmicRegisters_INT_MASK_2, 0xff);
// Clear interrupt status so we're not woken immediately (read the regs)
prv_clear_pending_interrupts();
// Set Reg_Standby_mod1 to specify which rails to turn off / keep on
// - SD1, LDO1 off
// - LDO2 on
// - Disable regulator pulldowns
prv_write_register(PmicRegisters_REG_STANDBY_MOD1, 0xa);
// Set standby_mode_on (bit 4) in ReferenceControl to 1 (See Fig. 78)
if (prv_register_set_bit(PmicRegisters_REFERENCE_CNTL, 4)) {
while (1) {}
__builtin_unreachable();
}
return false;
}
void set_ldo3_power_state(bool enabled) {
}
void set_4V5_power_state(bool enabled) {
}
void set_6V6_power_state(bool enabled) {
}

View file

@ -0,0 +1,111 @@
/*
* 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 "stm32f4xx_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,
FLASH_Sector_8, FLASH_Sector_9, FLASH_Sector_10, FLASH_Sector_11
};
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,
ADDR_FLASH_SECTOR_8, ADDR_FLASH_SECTOR_9, ADDR_FLASH_SECTOR_10, ADDR_FLASH_SECTOR_11
};
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,64 @@
/*
* 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 "stm32f4xx_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 */
#define ADDR_FLASH_SECTOR_8 ((uint32_t)0x08080000) /* Base @ of Sector 8, 128 Kbytes */
#define ADDR_FLASH_SECTOR_9 ((uint32_t)0x080A0000) /* Base @ of Sector 9, 128 Kbytes */
#define ADDR_FLASH_SECTOR_10 ((uint32_t)0x080C0000) /* Base @ of Sector 10, 128 Kbytes */
#define ADDR_FLASH_SECTOR_11 ((uint32_t)0x080E0000) /* Base @ of Sector 11, 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,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 "stm32f4xx_dbgmcu.h"
#include "stm32f4xx_iwdg.h"
#include "stm32f4xx_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,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);