aboutsummaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorTrygve Laugstøl <trygvis@inamo.no>2016-01-04 23:53:44 +0100
committerTrygve Laugstøl <trygvis@inamo.no>2016-01-04 23:53:44 +0100
commitec96951943921b57ef9c1e9dacb63e34716fe5b7 (patch)
treec85d1c0a063712459ad12144900eb522b39ef7a0 /apps
parentbaedda497d16c5096971eee83a0c467fe663fe6d (diff)
downloadstm32f103-playground-ec96951943921b57ef9c1e9dacb63e34716fe5b7.tar.gz
stm32f103-playground-ec96951943921b57ef9c1e9dacb63e34716fe5b7.tar.bz2
stm32f103-playground-ec96951943921b57ef9c1e9dacb63e34716fe5b7.tar.xz
stm32f103-playground-ec96951943921b57ef9c1e9dacb63e34716fe5b7.zip
o Actually working implementation of context switching.
It is important to remember to update the stack to the task descriptor on every switch!
Diffstat (limited to 'apps')
-rw-r--r--apps/CMakeLists.txt1
-rw-r--r--apps/cpp1/CMakeLists.txt19
-rw-r--r--apps/cpp1/cpp1.cpp88
-rw-r--r--apps/os2/os2.cpp307
-rw-r--r--apps/os2/os2_cm3.s42
-rw-r--r--apps/serial1/serial1.cpp4
6 files changed, 354 insertions, 107 deletions
diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt
index a38f85d..cbbbc65 100644
--- a/apps/CMakeLists.txt
+++ b/apps/CMakeLists.txt
@@ -1,3 +1,4 @@
+add_subdirectory(cpp1)
add_subdirectory(dma1)
add_subdirectory(os1)
add_subdirectory(os2)
diff --git a/apps/cpp1/CMakeLists.txt b/apps/cpp1/CMakeLists.txt
new file mode 100644
index 0000000..2580535
--- /dev/null
+++ b/apps/cpp1/CMakeLists.txt
@@ -0,0 +1,19 @@
+add_executable(cpp1.elf cpp1.cpp
+ ${STM32F10X_STDPERIPH_LIB}/Libraries/CMSIS/CM3/CoreSupport/core_cm3.c
+ ${STM32F10X_STDPERIPH_LIB}/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/system_stm32f10x.c
+ ${STM32F10X_STDPERIPH_LIB}/Libraries/STM32F10x_StdPeriph_Driver/src/misc.c
+ ${STM32F10X_STDPERIPH_LIB}/Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_rcc.c
+ ${STM32F10X_STDPERIPH_LIB}/Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_spi.c
+ ${STM32F10X_STDPERIPH_LIB}/Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_gpio.c
+ ${STM32F10X_STDPERIPH_LIB}/Libraries/STM32F10x_StdPeriph_Driver/src/stm32f10x_dma.c
+ $<TARGET_OBJECTS:playground>
+ )
+
+target_include_directories(cpp1.elf PUBLIC
+ $<TARGET_PROPERTY:playground,INTERFACE_INCLUDE_DIRECTORIES>
+ )
+target_compile_definitions(cpp1.elf PUBLIC ${STM32F10X_STDPERIPH_DEFINES})
+target_link_libraries(cpp1.elf tinyprintf)
+
+set_target_properties(cpp1.elf PROPERTIES LINK_FLAGS "-nostartfiles -T${CMAKE_SOURCE_DIR}/cmake/stm32.ld")
+add_extra_commands(cpp1.elf)
diff --git a/apps/cpp1/cpp1.cpp b/apps/cpp1/cpp1.cpp
new file mode 100644
index 0000000..5cea157
--- /dev/null
+++ b/apps/cpp1/cpp1.cpp
@@ -0,0 +1,88 @@
+#include <stdint.h>
+#include <stm32f10x.h>
+
+#include "debug.h"
+#include "tinyprintf.h"
+#include "playground.h"
+
+extern "C"
+__attribute__((naked, used))
+void HardFault_Handler_C(uint32_t *hardfault_args) {
+ dbg_printf("r0 = 0x%08lx (%lu)\n", hardfault_args[0], hardfault_args[0]);
+ dbg_printf("r1 = 0x%08lx (%lu)\n", hardfault_args[1], hardfault_args[1]);
+ dbg_printf("r2 = 0x%08lx (%lu)\n", hardfault_args[2], hardfault_args[2]);
+ dbg_printf("r3 = 0x%08lx (%lu)\n", hardfault_args[3], hardfault_args[3]);
+ dbg_printf("r12 = 0x%08lx (%lu)\n", hardfault_args[4], hardfault_args[4]);
+ dbg_printf("lr = 0x%08lx (%lu)\n", hardfault_args[5], hardfault_args[5]);
+ dbg_printf("pc = 0x%08lx (%lu)\n", hardfault_args[6], hardfault_args[6]);
+ dbg_printf("psr = 0x%08lx (%lu)\n", hardfault_args[7], hardfault_args[7]);
+ dbg_printf("\n");
+
+ halt();
+}
+
+volatile bool run = true;
+
+class StaticMyClass {
+public:
+ StaticMyClass(int value) : value(value) {
+ dbg_printf("StaticMyClass::StaticMyClass(%d)\n", value);
+ }
+
+ // Destructors are not supported for classes that are globally allocated.
+// ~StaticMyClass() {
+// }
+
+ int value;
+};
+
+class ConstStaticMyClass {
+public:
+ ConstStaticMyClass(int value) : value(value) {
+ dbg_printf("ConstStaticMyClass::ConstStaticMyClass(%d)\n", value);
+ }
+
+ int value;
+};
+
+class MyClass {
+public:
+ MyClass(int value) : value(value) {
+ dbg_printf("MyClass::MyClass(%d)\n", value);
+ }
+
+ ~MyClass() {
+ dbg_printf("MyClass::~MyClass\n");
+ }
+
+ int value;
+};
+
+StaticMyClass staticInstance(1337);
+static const ConstStaticMyClass constStaticInstance(9876);
+
+/*
+ * When we get there the stack pointer is set
+ */
+int main() {
+ SystemInit();
+
+ init_printf(nullptr, dbg_putc);
+
+ dbg_printf("C++ Test #1\n");
+
+ dbg_printf("staticInstance.value=%d\n", staticInstance.value);
+ dbg_printf("constStaticInstance.value=%d\n", constStaticInstance.value);
+
+ {
+ MyClass instance2(1234);
+ dbg_printf("instance2.value=%d\n", instance2.value);
+ }
+
+ dbg_printf("Sleeping..\n");
+ while (run) {
+ __NOP();
+ }
+
+ return 0;
+}
diff --git a/apps/os2/os2.cpp b/apps/os2/os2.cpp
index c104ac2..66c5b4d 100644
--- a/apps/os2/os2.cpp
+++ b/apps/os2/os2.cpp
@@ -1,7 +1,10 @@
#include <stdint.h>
+#include <cstddef>
+#include <type_traits>
#include <stm32f10x.h>
#include <stm32f10x_rcc.h>
#include <stm32f10x_gpio.h>
+#include <inttypes.h>
#include "debug.h"
#include "tinyprintf.h"
@@ -10,22 +13,12 @@
namespace trygvis {
namespace os2 {
-namespace os {
-extern "C"
-__attribute__((naked, used))
-void HardFault_Handler_C(uint32_t *hardfault_args) {
- dbg_printf("r0 = 0x%08lx (%lu)\n", hardfault_args[0], hardfault_args[0]);
- dbg_printf("r1 = 0x%08lx (%lu)\n", hardfault_args[1], hardfault_args[1]);
- dbg_printf("r2 = 0x%08lx (%lu)\n", hardfault_args[2], hardfault_args[2]);
- dbg_printf("r3 = 0x%08lx (%lu)\n", hardfault_args[3], hardfault_args[3]);
- dbg_printf("r12 = 0x%08lx (%lu)\n", hardfault_args[4], hardfault_args[4]);
- dbg_printf("lr = 0x%08lx (%lu)\n", hardfault_args[5], hardfault_args[5]);
- dbg_printf("pc = 0x%08lx (%lu)\n", hardfault_args[6], hardfault_args[6]);
- dbg_printf("psr = 0x%08lx (%lu)\n", hardfault_args[7], hardfault_args[7]);
- dbg_printf("\n");
+/*
+ * System configuration
+ */
+constexpr bool enable_stack_smashing_check = false;
- halt();
-}
+namespace os {
enum class exc_return_t : uint32_t {
RETURN_TO_HANDLER_MODE_USE_MSP = 0xFFFFFFF1,
@@ -35,20 +28,20 @@ enum class exc_return_t : uint32_t {
// This is used from assembly so order is important
struct task_t {
- uint8_t *stack;
+ uint8_t *current_stack;
+ // This field is used by the assembly code.
exc_return_t exc_return;
+ uint8_t *stack_start, *stack_end;
int flags;
- void init(uint8_t *stack) {
- this->stack = stack;
+ void init(uint8_t *current_stack, uint8_t *stack_start, uint8_t *stack_end) {
+ this->current_stack = current_stack;
+ this->stack_start = stack_start;
+ this->stack_end = stack_end;
flags = 0x01;
set_ready();
}
- void deinit() {
- flags = 0;
- }
-
bool is_ready() {
return (flags & 0x02) > 0;
}
@@ -56,15 +49,26 @@ struct task_t {
void set_ready() {
flags |= 0x02;
}
+
+ void set_blocked() {
+ flags &= ~0x02;
+ }
} __attribute__((packed));
-const int max_task_count = 3;
-const int stack_size = 100;
+// This is required for offsetof to be defined behaviour
+static_assert(std::is_standard_layout<task_t>::value, "task_t has to be is_standard_layout");
+static_assert(offsetof(task_t, exc_return) == 4, "task_t::exc_return has to be at offset 0");
+static_assert(offsetof(task_t, current_stack) == 0, "task_t::current_stack has to be at offset 4");
+
+const uint8_t max_task_count = 3;
+const int stack_size = 256;
+
+static_assert(stack_size % 4 == 0, "stack_size must be word-aligned.");
task_t tasks[max_task_count];
-uint8_t stacks[max_task_count][stack_size];
+uint8_t stacks[SizeOfArray(tasks)][stack_size];
uint8_t task_count = 0;
-int current_task;
+uint32_t current_task;
const unsigned int SYSTICK_FREQUENCY_HZ = 10;
@@ -91,6 +95,27 @@ struct software_frame_t {
};
extern "C"
+__attribute__((naked, used))
+void HardFault_Handler_C(uint32_t *stack) {
+ dbg_printf("r0 = 0x%08lx (%lu)\n", stack[0], stack[0]);
+ dbg_printf("r1 = 0x%08lx (%lu)\n", stack[1], stack[1]);
+ dbg_printf("r2 = 0x%08lx (%lu)\n", stack[2], stack[2]);
+ dbg_printf("r3 = 0x%08lx (%lu)\n", stack[3], stack[3]);
+ dbg_printf("r12 = 0x%08lx (%lu)\n", stack[4], stack[4]);
+ dbg_printf("lr = 0x%08lx (%lu)\n", stack[5], stack[5]);
+ dbg_printf("pc = 0x%08lx (%lu)\n", stack[6], stack[6]);
+ dbg_printf("psr = 0x%08lx (%lu)\n", stack[7], stack[7]);
+ dbg_printf("\n");
+
+ dbg_printf("current_task = %" PRIu32 "\n", current_task);
+ dbg_printf("\n");
+
+ Default_Handler();
+
+ halt();
+}
+
+extern "C"
void SysTick_Handler() {
static bool on = true;
@@ -106,14 +131,30 @@ void SysTick_Handler() {
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
}
+extern "C"
+void SVC_Handler() {
+ SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
+}
+
+/**
+ * Implemented in assembly, simply executes an SVC instruction.
+ */
+__attribute__((used))
+extern void reschedule();
+
+// This doesn't quite work.
void thread_end() {
+ dbg_printf("thread_end(). current_task=%" PRIu32 "\n", current_task);
}
+extern "C" void asm_idle_task(const void *const);
+
+#define idle_task asm_idle_task
volatile bool idle_task_run;
-void idle_task(const void *const) {
- // trigger PendSV to run
- SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
+void idle_task_c(const void *const) {
+ GPIOB->BSRR = GPIO_Pin_6;
+ GPIOB->BRR = GPIO_Pin_6;
// wait for the PendSV handler to kick in. After the handler has completed it will do a context switch and
// this point shouldn't be reached.
@@ -125,38 +166,27 @@ void idle_task(const void *const) {
}
}
-static
-void os_create_thread(void (task)(void const *const), bool create_sw);
-
-void os_init() {
- NVIC_SetPriority(SysTick_IRQn, 0xff);
- NVIC_SetPriority(PendSV_IRQn, 0xff);
-
- SysTick_Config(SystemCoreClock / SYSTICK_FREQUENCY_HZ);
+static void init_stack(uint8_t *stack, size_t stack_size) {
+ static int stack_pattern = -16;
- GPIO_InitTypeDef init;
- GPIO_StructInit(&init);
- init.GPIO_Mode = GPIO_Mode_Out_PP;
- init.GPIO_Pin = GPIO_Pin_6;
- GPIO_Init(GPIOB, &init);
- init.GPIO_Pin = GPIO_Pin_7;
- GPIO_Init(GPIOB, &init);
-
- for (int i = 0; i < max_task_count; i++) {
- tasks[i].flags = 0;
+ for (size_t y = 0; y < stack_size; y++) {
+ stack[y] = (uint8_t) stack_pattern;
}
- os_create_thread(idle_task, false);
- current_task = 0;
-}
+ stack_pattern -= 16;
-void os_create_thread(void (task)(void const *const)) {
- os_create_thread(task, true);
+ auto x = idle_task_c;
+ (void) x;
}
-static
-void os_create_thread(void (task)(void const *const), bool create_sw) {
- uint8_t *s = stacks[task_count] + stack_size;
+//static
+void os_create_thread(void (task)(void const *const), bool create_sw = true) {
+ uint8_t *const stack_end = stacks[task_count];
+ uint8_t *const stack_start = stack_end + stack_size;
+
+ init_stack(stack_end, stack_size);
+
+ uint8_t *s = stack_start;
s -= sizeof(hardware_frame_t);
hardware_frame_t *hw = reinterpret_cast<hardware_frame_t *>(s);
@@ -186,37 +216,69 @@ void os_create_thread(void (task)(void const *const), bool create_sw) {
}
task_t *t = &tasks[task_count];
- t->init(s);
+ t->init(s, stack_start, stack_end);
t->exc_return = exc_return_t::RETURN_TO_THREAD_MODE_USE_PSP;
+ dbg_printf("task #%d: stack=%p -> %p, current=%p, allocated=%d\n", task_count,
+ static_cast<void *>(t->stack_end), static_cast<void *>(t->stack_start),
+ static_cast<void *>(t->current_stack), int(t->stack_start - t->current_stack));
+
task_count++;
}
static
-int find_first_ready_task() {
+uint32_t find_first_ready_task() {
task_t *t;
- int idx = current_task + 1;
+ // Start from the task after the current one
+ uint32_t idx = current_task;
do {
+ idx++;
+ // If we have checked the entire array, start from the first non-idle task
if (idx == max_task_count) {
idx = 1;
- } else if (idx == current_task) {
- return 0;
}
t = &tasks[idx];
+ if (idx == current_task) {
+ // If we have checked all other tasks, use the current one if it is still ready. if not, use the idle task
+ if (!t->is_ready()) {
+ idx = 0;
+ }
+ break;
+ }
+
} while (!t->is_ready());
return idx;
}
+template<bool enable>
+void check_stack_smashing(task_t *);
+
+template<>
+void check_stack_smashing<false>(task_t *) {
+}
+
+// TODO: this implementation hasn't been checked.
+template<>
+void check_stack_smashing<true>(task_t *t) {
+ if (t->current_stack < t->stack_end) {
+ dbg_printf("STACK SMASHED: task #%" PRIu32 ", end=%p, current=%p\n", current_task,
+ static_cast<void *>(t->current_stack), static_cast<void *>(t->current_stack));
+ }
+}
+
__attribute__((used))
task_t *select_next_task(uint8_t *current_stack) {
task_t *t = &tasks[current_task];
- int new_task = find_first_ready_task();
+ t->current_stack = current_stack;
+
+ check_stack_smashing<enable_stack_smashing_check>(t);
+
+ uint32_t new_task = find_first_ready_task();
if (new_task != current_task) {
- t->stack = current_stack;
t = &tasks[new_task];
current_task = new_task;
}
@@ -228,61 +290,100 @@ volatile bool run;
extern "C" void do_first_context_switch(uint8_t *user_stack, void (task)(const void *const arg));
+void os_init() {
+ NVIC_SetPriority(SysTick_IRQn, 0xff);
+ NVIC_SetPriority(PendSV_IRQn, 0xff);
+
+ SysTick_Config(SystemCoreClock / SYSTICK_FREQUENCY_HZ);
+
+ GPIO_InitTypeDef init;
+ GPIO_StructInit(&init);
+ init.GPIO_Mode = GPIO_Mode_Out_PP;
+ init.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
+ GPIO_Init(GPIOB, &init);
+
+ for (size_t i = 0; i < SizeOfArray(tasks); i++) {
+ tasks[i].flags = 0;
+ }
+
+ os_create_thread(idle_task, false);
+ current_task = 0;
+}
+
void os_start() {
run = true;
task_t &t = tasks[0];
- do_first_context_switch(t.stack, idle_task);
+ // TODO: this should jump to runnable user task if available
+ do_first_context_switch(t.current_stack, idle_task);
while (run) {
}
}
-class CriticalSection {
+//class DisabledInterrupts {
+//public:
+// DisabledInterrupts() : primask(__get_PRIMASK()) {
+// __disable_irq();
+// }
+//
+// ~DisabledInterrupts() {
+// __set_PRIMASK(primask);
+// }
+//
+//private:
+// uint32_t primask;
+//};
+
+class Mutex final {
public:
- CriticalSection() : primask(__get_PRIMASK()) {
- __disable_irq();
+ Mutex() : owner(UINT32_MAX) {
}
- ~CriticalSection() {
- __set_PRIMASK(primask);
+ void lock() {
+ uint32_t old;
+
+ do {
+ // read the semaphore value
+ old = __LDREXW(&owner);
+ // loop again if it is locked and we are blocking
+ // or setting it with strex failed
+ } while ((old == current_task) || __STREXW(current_task, &owner) != 0);
+ }
+
+ void unlock() {
+ owner = UINT32_MAX;
}
private:
- uint32_t primask;
+ uint32_t owner;
};
-//class Mutex {
-//public:
-// Mutex() : task(-1) {
-// }
-//
-// void lock() {
-// do {
-// {
-// CriticalSection cs;
-// if (task == -1) {
-// task = current_task;
-// break;
-// }
-// }
-//
-// } while (true);
-// }
-//
-//private:
-// int task;
-//};
+class LockMutex {
+public:
+ LockMutex(Mutex &mutex) : mutex(mutex) {
+ mutex.lock();
+ }
+
+ ~LockMutex() {
+ mutex.unlock();
+ }
+
+private:
+ Mutex &mutex;
+};
} // namespace os
-namespace main {
+namespace app {
-using namespace trygvis::os2::os;
+namespace os = trygvis::os2::os;
volatile bool run1 = true;
+os::Mutex mutex;
+
void job1(void const *const) {
GPIO_InitTypeDef init;
GPIO_StructInit(&init);
@@ -291,8 +392,9 @@ void job1(void const *const) {
GPIO_Init(GPIOB, &init);
while (run1) {
- GPIO_SetBits(GPIOB, GPIO_Pin_8);
- GPIO_ResetBits(GPIOB, GPIO_Pin_8);
+ os::LockMutex ms(mutex);
+ GPIOB->BSRR = GPIO_Pin_8;
+ GPIOB->BRR = GPIO_Pin_8;
}
}
@@ -306,9 +408,9 @@ void job2(void const *const) {
GPIO_Init(GPIOB, &init);
while (run2) {
- CriticalSection cs;
- GPIO_SetBits(GPIOB, GPIO_Pin_5);
- GPIO_ResetBits(GPIOB, GPIO_Pin_5);
+ os::LockMutex ms(mutex);
+ GPIOB->BSRR = GPIO_Pin_5;
+ GPIOB->BRR = GPIO_Pin_5;
}
}
@@ -317,6 +419,7 @@ int main(void) {
SystemInit();
init_printf(nullptr, dbg_putc);
+ dbg_printf("os2\n");
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO
| RCC_APB2Periph_USART1
@@ -328,14 +431,14 @@ int main(void) {
RCC_APB2PeriphResetCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphResetCmd(RCC_APB2Periph_GPIOB, DISABLE);
- os_init();
- os_create_thread(job1);
- os_create_thread(job2);
- os_start();
+ os::os_init();
+ os::os_create_thread(job1);
+ os::os_create_thread(job2);
+ os::os_start();
return 0;
}
-} // namespace main
+} // namespace app
} // namespace os2
} // namespace trygvis
diff --git a/apps/os2/os2_cm3.s b/apps/os2/os2_cm3.s
index dabdfe0..aa18703 100644
--- a/apps/os2/os2_cm3.s
+++ b/apps/os2/os2_cm3.s
@@ -2,6 +2,15 @@
.cpu cortex-m3
.thumb
+.equ PERIPH_BASE , 0x40000000
+.equ APB2PERIPH_BASE , PERIPH_BASE + 0x10000
+.equ GPIOB_BASE , APB2PERIPH_BASE + 0x0C00
+
+.equ GPIO_BSRR_OFF , 0x10
+.equ GPIO_BSR_OFF , 0x14
+
+# .equ GPIOB 0x40010c00
+
.section .text
/*
@@ -18,7 +27,14 @@ do_first_context_switch:
msr psp, r0
// Set CONTROL.SPSEL=1 so that we run with two stacks
- mov r0, #2
+ mrs r0, control
+ orr r0, #2
+ msr control, r0
+ isb
+
+ // Set CONTROL.nPRIV=1 so that we run with in unprivileged mode
+ mrs r0, control
+ orr r0, #1
msr control, r0
isb
@@ -28,6 +44,27 @@ do_first_context_switch:
pop {r4, r5}
bx r4
+.thumb_func
+.global _ZN7trygvis3os22os10rescheduleEv
+_ZN7trygvis3os22os10rescheduleEv:
+ svc #0
+ bx lr
+
+// A very simple idle task, just to know exactly which registers that are used and what their values are supposed to be.
+// Toggles GPIO B, pin #6 on a STM32F103
+.thumb_func
+.global asm_idle_task
+asm_idle_task:
+ ldr r0, =0x0020
+ ldr r0, =0xffff
+ ldr r1, =GPIOB_BASE
+asm_idle_task_loop:
+ str r0, [r1, #GPIO_BSRR_OFF]
+ str r0, [r1, #GPIO_BSR_OFF]
+ b asm_idle_task_loop
+.pool
+.size asm_idle_task,.-asm_idle_task
+
/*
When this function is executed {r0-r3,r12,lr,pc} has been pushed to the stack pointed to by PSP. We push the rest of the
registers to the PSP. The current stack pointer is the MSP.
@@ -42,9 +79,10 @@ PendSV_Handler:
// Call select_next_task_sp. after return, r0 points to a task_t
bl _ZN7trygvis3os22os16select_next_taskEPh // task_t *select_next_task(uint8_t *current_stack)
- // load task_t.stack and task_t.lr into r1 and lr
+ // load task_t.exc_return and task_t.current_stack into r1 and lr
ldm r0, {r1, lr}
+ // Restore the registers saved on the thread's stack
ldmia r1!, {r4 - r11}
msr psp, r1
diff --git a/apps/serial1/serial1.cpp b/apps/serial1/serial1.cpp
index 60e9bc9..e93286c 100644
--- a/apps/serial1/serial1.cpp
+++ b/apps/serial1/serial1.cpp
@@ -3,17 +3,15 @@
#include <stm32f10x_rcc.h>
#include <stm32f10x_gpio.h>
#include <stddef.h>
-#include <stdarg.h>
#include "debug.h"
#include "tinyprintf.h"
extern "C" void halt();
-#include "stm32f10x_conf.h"
-
extern "C"
__attribute__((naked))
void HardFault_Handler_C(uint32_t *hardfault_args) {
+ (void) hardfault_args;
halt();
}