Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ set(MICRO_FORGE_SOURCES
src/chips/stm32f1/peripheral_config.cpp
src/chips/stm32f1/stm32f103_soc.cpp
src/chips/stm32f1/stm32f1_afio.cpp
src/chips/stm32f1/stm32f1_exti.cpp
src/chips/stm32f1/stm32f1_flash.cpp
src/chips/stm32f1/stm32f1_gpio.cpp
src/chips/stm32f1/stm32f1_rcc.cpp
Expand Down
30 changes: 30 additions & 0 deletions document/milestones/06-progress-assessment-and-replan.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,36 @@

**02 仍剩**:仅 tail-chaining(同步模拟器天然退化,见上)。**03 外设中断路径**(EXTI / Timer UIF→IRQ E2E / USART RX-RXNE-TXE IRQ)进入第二波 C1–C3。

## 实施记录(2026-06-23 · 第二波 C2a/b)

继 Thumb-2 全覆盖里程碑收口(T0-T4 + T5c,notes 012/013)后,焦点转第二波外设中断端到端。C2 Timer 落地两件事:

- **打通 `raise_irq` 公共通道**:原 `CortexM3CPU::raise_irq` 是 no-op(`return {};`)—— **整个外设→NVIC 注入通道从未接通**(SysTick 靠独立 `sys_tick_irq()` 绕过)。实现为 `nvic_->set_pending(irq)` 一行;这是所有 MMIO IRQ(TIM/USART/EXTI)的公共入口。
- **Timer UIF → NVIC → handler 端到端**:Timer 仿 SysTick `set_irq_callback` 模式,`tick()` 在 UIF **0→1 edge + `DIER.UIE`** 时回调一次;SoC 接 `raise_irq(kTim2Irqn=28)`;`kTim2Irqn` 常量落 interrupt_config.hpp(vector index 44)。
- 验证:单元(edge/UIE 语义)+ `TimerUifRoundtrip` E2E(coordinator Apb1 驱动 tick → handler 往返)。**289/289 绿**,固件 E2E/CLI 无回归。细节见 notes 014。
- **C2 进度**:Timer UIF→IRQ E2E 由 ~50%(UIF 产生但无端到端)提升到通道打通 + 往返验证。剩 C2c 抢占/嵌套场景(检验第一波 A 的 `active_priorities_`),再 C1 EXTI / C3 USART RX-IRQ。

## 实施记录(2026-06-23 · 第二波 C1 EXTI)

继 C2 打通 raise_irq 公共通道后,EXTI 作为第二个消费者落地(证通道通用):

- **EXTI 控制器**(`Stm32f1Exti` @0x40010400):IMR/EMR/RTSR/FTSR/SWIER/PR(PR rc_w1)。GPIO 边沿 → AFIO EXTICR 路由校验 → IMR+RTSR/FTSR 沿匹配 → set PR + raise(线→IRQ:0-4=6-10,5-9=23,10-15=40)。
- **AFIO EXTICR 终于有消费者**:加 `exti_line_port(line)` getter,EXTI 据此路由。
- **GPIO simulate_input 补 edge emit**:原只 ODR 变化 emit;EXTI 监听外部输入边沿,故输入边沿同路径喂 EXTI。
- **SoC 接线**:`gpioa/b/c.edge_signal()` → EXTI;EXTI → `raise_irq`(复用 C2 通道)。
- 验证:6 单元(寄存器/PR/路由/屏蔽/沿选择)+ `ExtiGpioEdgeRoundtrip` E2E(simulate_input PA2 → handler 往返)。**296/296 绿**,无回归。细节 notes 015。
- **坑**:`MICRO_FORGE_SOURCES` 是显式 `set()` 列表(非 GLOB_RECURSE,DIRECTIVES A 描述不准),新 `src/*.cpp` 须手动加 CMakeLists。C2c 抢占验证边际价值低(test_interrupt 已覆盖),跳过。
- 06 进度:EXTI 0% → 端到端通。剩 C3 USART RX-IRQ(raise_irq 第三消费者)。

## 实施记录(2026-06-23 · 第二波 C3 USART RX,第二波收尾)

- **USART RX 注入 + RXNEIE**:USART 加 `inject_rx(byte)`(单字节缓冲 + SR.RXNE bit5),DR read 返回 rx 字节 + 清 RXNE(读=RX/写=TX 共享地址)。RXNEIE(CR1 bit5)+ RXNE → raise USART1(IRQ37)。
- **raise_irq 第三消费者**:TIM(C2)/EXTI(C1)/USART(C3)三外设全通同一通道 —— 证明 C2 打通的通道通用。
- **TXEIE 跳过**:模拟器 TX 即时,TXE 常高 → TXEIE 会循环 raise(无 TX 延迟可消耗);固件轮询 TXE 位仍工作。
- 验证:4 单元(RXNE/DR/RXNEIE enabled/disabled)+ `UsartRxRoundtrip` E2E。**301/301 绿**,无回归。细节 notes 016。
- **坑**:ARM 异常自动压栈 r0-r3,handler 改它们被返回 POP 覆盖;测试读 handler 结果须用 r4+(不自动压栈)。IRQ≥32 在 ISER1,IPR IRQ37 在 0xE000E424 byte1 需 shift。
- **第二波全部完成**(C1/C2/C3 + C4 bit-band 早前):06 第二波「外设中断端到端」收口,raise_irq 通道三消费者(TIM/EXTI/USART)全通。下一里程碑候选:第三波 DMA/SPI/FLASH、04 GUI、02 收尾。


## 结论 / 下一步

Expand Down
37 changes: 37 additions & 0 deletions document/notes/014-timer-uif-irq-e2e.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 014 — Timer UIF → NVIC → handler 端到端(C2a/b)

> 06 第二波 C2(外设中断端到端)。打通外设→NVIC 的 IRQ 注入通道(原 `raise_irq` 空壳),Timer UIF 经 edge 回调 + UIE 使能触发,coordinator 驱动 tick → handler 往返 E2E 验证。ctest **289/289 绿**(286 + 3 新)。下一步 C2c 抢占/嵌套验证。

## 背景

06 第二波:外设中断路径紧跟第一波 A(抢占)做端到端验证 ——「中断在真实外设上跑通是抢占正确性的最佳验证手段」。Timer 已半成(UIF 产生、PSC/ARR/CNT 与 VirtualClock 联动),缺 UIF→NVIC→handler 端到端。

## 核心发现:raise_irq 是空壳

`CortexM3CPU::raise_irq` 原为 no-op(`return {};`)—— **整个外设→NVIC 通道从未接通**。SysTick 靠独立的 `sys_tick_irq()`(直设 `pending_sys_tick_`,系统异常 15)绕过它。所以 C2 不只是「Timer 没调 raise」,而是**第一个打通这条公共通道**的里程碑(USART/EXTI 以后都走它)。

## 实现

1. **`raise_irq`**([cortex_m3.cpp](src/arch/arm/cortex_m3/cortex_m3.cpp)):`if (nvic_) nvic_->set_pending(irq);`。一行接通 NVIC ISPR。
2. **Timer UIE edge 回调**([stm32f1_timer.hpp](include/chips/stm32f1/stm32f1_timer.hpp) / [.cpp](src/chips/stm32f1/stm32f1_timer.cpp)):仿 SysTick `set_irq_callback(std::function<void()>)`。`tick()` 在 UIF **0→1 edge 且 `DIER.UIE`(bit0)使能**时调回调一次。
3. **`kTim2Irqn=28`**([interrupt_config.hpp](include/chips/stm32f1/interrupt_config.hpp)):`inline constexpr intr::intr_n_t kTim2Irqn = 28`(vector index 16+28=44)。
4. **SoC 接线**([stm32f103_soc.cpp](src/chips/stm32f1/stm32f103_soc.cpp)):`tim2.set_irq_callback([cm3_weak]{ (void)cm3_weak->raise_irq(kTim2Irqn); })`,仿 SysTick 接线(WeakPtr + IsValid 守卫)。

## 验证(3 新单测)

- **单元**([test_stm32f1_periph.cpp](test/test_stm32f1_periph.cpp)):
- `UifEdgeWithUieTriggersIrqCallback`:edge+UIE → 回调一次;UIF 未清时再溢出不重 fire(edge);清 UIF 后重新 fire。
- `UifWithoutUieDoesNotFireCallback`:UIF 置位但 UIE 未使能 → 不回调(只状态,无 IRQ)。
- **E2E**([test_interrupt_roundtrip.cpp](test/test_interrupt_roundtrip.cpp) `TimerUifRoundtrip`):fixture 加 TIM2(0x40000000)+ NVIC enable bit28 + prio 0xE0 + vector[44];配 PSC=0/ARR=5/UIE/CEN;coordinator(Apb1)驱动 tick → UIF → raise_irq(28) → handler 进入 → BX LR 返回。断言 entered + returned。
- `ctest` 全量 **289/289 绿**,固件 E2E/CLI/中断抢占无回归。

## 陷阱

- **`raise_irq` nodiscard**:返回 `expected<void>`,丢弃触发 `-Wunused-value`(-Werror)。调用点(SoC 回调、test)须 `(void)` 包裹。SysTick 的 `sys_tick_irq` 返回 void 无此问题 —— 外设 IRQ 通道首次遇到。
- **edge vs level**:选 edge(UIF 0→1 raise 一次)。NVIC `set_pending` 幂等,但 edge 语义干净(避免每 tick 重复调回调);固件不清 UIF 导致持续 pending 是固件行为非 bug。
- **Apb1 时钟域**:Timer 是 tickable on Apb1,coordinator 每 step 按 Apb1 频率给 cycle。ARR=5/PSC=0 在数 step 内触发(SysTick Sysclk 类比)。
- **IPR 偏移**:TIM2=IRQ28,优先级寄存器 `0xE000E400+28`(=0xE000E41C,word 对齐,byte0=IRQ28 prio);ISER0 bit28 enable;vector index 16+28=44。

## 成果 / 下一步

`raise_irq` 公共通道打通 + Timer UIF 端到端往返验证。**C2c**(下批):抢占/嵌套场景(高优先级 Timer IRQ 抢占 Thread / 嵌套),检验第一波 A 的 `active_priorities_` 栈 —— 这是 C2 的核心价值。之后 C1 EXTI / C3 USART RX-IRQ 复用同通道。
33 changes: 33 additions & 0 deletions document/notes/015-exti-external-interrupt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 015 — EXTI 外部中断 + AFIO EXTICR(C1)

> 06 第二波 C1。新增 EXTI 控制器(IMR/EMR/RTSR/FTSR/SWIER/PR),GPIO 边沿经 AFIO EXTICR 路由 → 触发 → raise NVIC(复用 C2 的 raise_irq 通道,EXTI 是其第二个消费者)。GPIO `simulate_input` 改为也 emit edge(外部输入边沿喂 EXTI)。ctest **296/296 绿**(289 + 7 新)。

## 背景

06 第二波 C1:GPIO 外部中断模式。AFIO EXTICR 寄存器已存但无消费者,缺 EXTI 控制器(06 实测 EXTI 0%)。复用 C2 打通的 raise_irq 通道 —— EXTI 作为第二个消费者,证明该通道通用(TIM/EXTI/USART 都走它)。

## 实现

1. **`Stm32f1Exti : Device`**(@0x40010400):6 寄存器 IMR/EMR/RTSR/FTSR/SWIER/PR(PR rc_w1,写 1 清)。`on_gpio_edge(GpioEdge)`:线=pin → EXTICR 路由校验 port → IMR 使能 + RTSR/FTSR 沿匹配 → set PR + raise(经 `exti_irq_for_line`)。
2. **AFIO `exti_line_port(line)`**([stm32f1_afio.hpp](include/chips/stm32f1/stm32f1_afio.hpp)):暴露 EXTICR 路由(线 N → 端口 0=PA,1=PB,…)。原 `exticr_[4]` 私有无 getter。
3. **GPIO `simulate_input` emit edge**([stm32f1_gpio.cpp](src/chips/stm32f1/stm32f1_gpio.cpp)):原只改 `idr_` 不 emit;EXTI 监听外部输入边沿,故现也 emit edge_signal(与 ODR 边沿同路径)。
4. **线→IRQ**(`exti_irq_for_line`,EXTI hpp):0-4=6-10,5-9=23(EXTI9_5),10-15=40(EXTI15_10)。
5. **SoC 接线**([stm32f103_soc.cpp](src/chips/stm32f1/stm32f103_soc.cpp)):`exti.set_afio(afio)` + `gpioa/b/c.edge_signal().connect(exti slot)` + `exti.set_irq_callback(raise)`。

## 验证(7 新单测)

- **单元**([test_stm32f1_periph.cpp](test/test_stm32f1_periph.cpp)):寄存器 R/W、PR w1c、上升沿触发 + IRQ 号、错端口不触发、IMR 屏蔽、FTSR-only 不响应上升沿。
- **E2E**([test_interrupt_roundtrip.cpp](test/test_interrupt_roundtrip.cpp) `ExtiGpioEdgeRoundtrip`):`simulate_input` PA2 上升沿 → EXTI → raise IRQ8 → handler 进入 → BX LR 返回。
- `ctest` 全量 **296/296 绿**,固件 E2E/CLI/中断抢占无回归。

## 陷阱

- **`MICRO_FORGE_SOURCES` 是显式 `set()` 列表**(CMakeLists.txt 行 25),**非 DIRECTIVES A 说的 `GLOB_RECURSE`**:新 `src/*.cpp` 须手动加该列表,否则 `vtable undefined` link error(reconfigure 也不会自动扫)。
- **IPR word 对齐**:EXTI 线优先级寄存器 `0xE000E400+N`(byte N);word 写须 4 对齐(IRQ8 在 0xE000E408 byte0;IRQ9 在 byte1 需 `<<8`)。测试选 IRQ8(word 对齐)避 unaligned。
- **simulate_input vs ODR edge**:原 `edge_signal` 只 ODR 变化 emit;EXTI 要外部输入,故补 `simulate_input` edge emit(输入边沿同路径喂 EXTI)。
- **EXTICR 路由**:EXTI 线 N 路由到 `EXTICR[N/4]` bits[(N%4)*4 : +4] 选的端口;错端口的 edge 不触发。
- **C2c 跳过**:抢占/嵌套验证边际价值低(Thread 被抢占已由 `TimerUifRoundtrip` 验证 + `active_priorities_` 已被 test_interrupt 5 测试覆盖),本里程碑不补。

## 成果

EXTI 外部中断端到端(GPIO 边沿 → EXTI → NVIC → handler),AFIO EXTICR 终于有消费者,raise_irq 通道第二个用户(证通用)。剩 C3 USART RX-IRQ(第三消费者,需先设计串口输入注入)。
27 changes: 27 additions & 0 deletions document/notes/016-usart-rx-injection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 016 — USART RX 注入 + RXNE 中断(C3)

> 06 第二波 C3(收尾)。USART 加 RX 注入(`inject_rx` 单字节 + RXNE)+ RXNEIE 中断 → raise USART1(IRQ37)。raise_irq **第三消费者**(TIM/EXTI/USART 全通)。TXEIE 跳过(模拟器 TX 即时,TXE 常高会循环 raise)。ctest **301/301 绿**(296 + 5 新)。

## 实现

1. **USART `inject_rx(byte)`**:设 `rx_dr_` + SR.RXNE(bit5);RXNEIE(CR1 bit5)使能 → `irq_callback`。
2. **DR read 返回 rx_dr_ + 清 RXNE**(硬件 DR 读=RX/写=TX 共享地址)。
3. **CR1 write 使能 RXNEIE 时若 RXNE 已置** → 立即 raise(固件先收字节后开中断的场景)。
4. **`kUsart1Irqn=37`**(interrupt_config)。SoC `usart1.set_irq_callback(raise 37)`。

## 验证(5 新)

- **单元**([test_stm32f1_periph.cpp](test/test_stm32f1_periph.cpp)):`inject_rx`→RXNE、DR read 清+返回字节、RXNEIE enabled→callback、disabled→不 callback。
- **E2E**([test_interrupt_roundtrip.cpp](test/test_interrupt_roundtrip.cpp) `UsartRxRoundtrip`):`inject_rx('A')` → USART1 IRQ37 → handler `ldr r4,[r1]` 读 DR → r4='A'。
- `ctest` 全量 **301/301 绿**,无回归。

## 陷阱

- **ARM 异常模型**:exception entry 自动压栈 r0-r3/r12/lr/pc/xpsr,exception return POP 恢复。handler 改 r0-r3 会被返回覆盖。测试读 handler 结果须用 **r4-r11**(不自动压栈,返回后保留;handler 本应 push/pop 保护 —— 测试简化可省)。
- **IRQ≥32 在 ISER1/ISPR1**:NVIC ISER `idx=offset/4`,ISER1(0x004)enable IRQ32+(37→bit5)。IPR IRQ37 在 `0xE000E424`(IPR9)byte1,word 写需 `<<8`。
- **TXEIE 跳过**:模拟器 TX 即时完成,TXE 常高(`0xC0`);TXEIE 会循环 raise(无 TX 移位延迟可消耗)。固件用 TXEIE 时轮询 TXE 位仍工作。MVP 不做 TXEIE。
- **USART DR 共享**:读 DR=返回 RX+清 RXNE;写 DR=TX(原逻辑)。`dr_` 成员 write 存但 read 不返回(无害)。

## 成果

第二波外设中断端到端**全部完成**:raise_irq 公共通道(C2)+ TIM(C2)+ EXTI(C1)+ USART RX(C3)三消费者全通同一通道。06 第二波 C1/C2/C3 + C4(bit-band 早前)✅。下一里程碑候选:第三波 DMA/SPI/FLASH、04 GUI dashboard、02 收尾。
6 changes: 6 additions & 0 deletions include/chips/stm32f1/interrupt_config.hpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
#pragma once

#include "cpu/intr.hpp"
#include "memory/bus.hpp"
#include "periph/nvic.hpp"
#include "periph/scb.hpp"
#include "periph/systick.hpp"

namespace micro_forge::chips::stm32f1 {

// STM32F103 external IRQ numbers (Cortex-M exception base is 16, so TIM2 IRQ 28
// lives at vector-table index 16+28 = 44). Source: STM32F1 vector table.
inline constexpr intr::intr_n_t kTim2Irqn = 28;
inline constexpr intr::intr_n_t kUsart1Irqn = 37;

Expected<void> configure_interrupt_devices(memory::Bus& bus,
periph::NvicPeripheral& nvic,
periph::SysTickPeripheral& systick,
Expand Down
2 changes: 2 additions & 0 deletions include/chips/stm32f1/stm32f103_soc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "chips/machine.hpp"
#include "chips/stm32f1/clock_domains.hpp"
#include "chips/stm32f1/stm32f1_afio.hpp"
#include "chips/stm32f1/stm32f1_exti.hpp"
#include "chips/stm32f1/stm32f1_flash.hpp"
#include "chips/stm32f1/stm32f1_gpio.hpp"
#include "chips/stm32f1/stm32f1_rcc.hpp"
Expand Down Expand Up @@ -45,6 +46,7 @@ struct Stm32f103Parts {
Stm32f1Gpio gpioc{'C'};
Stm32f1Usart usart1;
Stm32f1Timer tim2;
Stm32f1Exti exti;

Stm32f103Parts() = default;

Expand Down
4 changes: 4 additions & 0 deletions include/chips/stm32f1/stm32f1_afio.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ class Stm32f1Afio : public periph::Device {
Expected<void> write(addr_t offset, data_t data, Width w) override;
std::string_view name() const noexcept override { return "AFIO"; }

// EXTI line N → owning GPIO port index (0=PA,1=PB,2=PC,3=PD,4=PE),
// decoded from EXTICR. Consumed by Stm32f1Exti to route GPIO edges.
uint8_t exti_line_port(uint8_t line) const;

WeakPtr<Stm32f1Afio> GetWeak() { return weak_factory_.GetWeakPtr(); }

private:
Expand Down
72 changes: 72 additions & 0 deletions include/chips/stm32f1/stm32f1_exti.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#pragma once

#include "cpu/intr.hpp"
#include "hooks/events.hpp"
#include "periph/device.hpp"
#include "util/weak_ptr/weak_ptr_factory.h"

#include <cstdint>
#include <functional>

namespace micro_forge::chips::stm32f1 {

class Stm32f1Afio;

// STM32F1 External Interrupt/Event controller (EXTI), mapped at 0x40010400.
// Each EXTI line 0..15 is routed to one GPIO port via AFIO EXTICR; a configured
// edge on that port's pin sets the pending bit and raises the line's NVIC IRQ.
class Stm32f1Exti : public periph::Device {
public:
Stm32f1Exti() = default;

// EXTI needs AFIO EXTICR to map a line number to the owning GPIO port.
void set_afio(Stm32f1Afio& afio) { afio_ = &afio; }

// Peripheral → CPU IRQ channel: invoked once per line that goes pending.
void set_irq_callback(std::function<void(intr::intr_n_t)> cb) {
irq_cb_ = std::move(cb);
}

// GPIO edge slot — subscribe this to each GPIO's edge_signal().
void on_gpio_edge(const hooks::GpioEdge& edge);

// Device
Expected<data_t> read(addr_t offset, Width w) override;
Expected<void> write(addr_t offset, data_t data, Width w) override;
std::string_view name() const noexcept override { return "EXTI"; }

bool pending(uint8_t line) const { return (pr_ >> line) & 1u; }

WeakPtr<Stm32f1Exti> GetWeak() { return weak_factory_.GetWeakPtr(); }

private:
uint32_t imr_ = 0; // Interrupt mask (line enable)
uint32_t emr_ = 0; // Event mask (stored; v1 has no event consumer)
uint32_t rtsr_ = 0; // Rising-edge trigger select
uint32_t ftsr_ = 0; // Falling-edge trigger select
uint32_t swier_ = 0; // Software interrupt trigger
uint32_t pr_ = 0; // Pending (rc_w1: write 1 to clear)

Stm32f1Afio* afio_ = nullptr;
std::function<void(intr::intr_n_t)> irq_cb_;

WeakPtrFactory<Stm32f1Exti> weak_factory_{this};

void trigger_line(uint8_t line);
};

// EXTI line N → NVIC IRQ number (STM32F1 vector table). Lines 0-4 have
// individual IRQs (6-10); 5-9 share EXTI9_5 (23); 10-15 share EXTI15_10 (40).
inline intr::intr_n_t exti_irq_for_line(uint8_t line) {
switch (line) {
case 0: return 6;
case 1: return 7;
case 2: return 8;
case 3: return 9;
case 4: return 10;
case 5: case 6: case 7: case 8: case 9: return 23;
default: return 40; // 10-15
}
}

} // namespace micro_forge::chips::stm32f1
6 changes: 6 additions & 0 deletions include/chips/stm32f1/stm32f1_timer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "util/weak_ptr/weak_ptr_factory.h"

#include <cstdint>
#include <functional>

namespace micro_forge::chips::stm32f1 {

Expand All @@ -26,6 +27,9 @@ class Stm32f1Timer : public periph::Device, public periph::Timer {
bool update_flag() const override;
void clear_update_flag() override;

// Peripheral → CPU IRQ channel: invoked on UIF 0→1 edge when DIER.UIE is set.
void set_irq_callback(std::function<void()> cb) { irq_cb_ = std::move(cb); }

WeakPtr<Stm32f1Timer> GetWeak() { return weak_factory_.GetWeakPtr(); }

private:
Expand All @@ -37,6 +41,8 @@ class Stm32f1Timer : public periph::Device, public periph::Timer {
uint32_t cnt_ = 0;
uint64_t prescaler_residual_ = 0;

std::function<void()> irq_cb_;

WeakPtrFactory<Stm32f1Timer> weak_factory_{this};
};

Expand Down
10 changes: 10 additions & 0 deletions include/chips/stm32f1/stm32f1_usart.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ class Stm32f1Usart : public periph::Device, public periph::SerialPort {
bool can_send() const override;
void set_output(OutputCallback cb) override;

// RX injection (host → firmware): buffers a received byte, sets RXNE, and
// raises the USART IRQ if RXNEIE (CR1 bit5) is enabled.
void inject_rx(uint8_t byte);

// Peripheral → CPU IRQ channel (SoC wires this to raise the USART IRQ).
void set_irq_callback(std::function<void()> cb) { irq_cb_ = std::move(cb); }

WeakPtr<Stm32f1Usart> GetWeak() { return weak_factory_.GetWeakPtr(); }

private:
Expand All @@ -33,7 +40,10 @@ class Stm32f1Usart : public periph::Device, public periph::SerialPort {
uint32_t cr2_ = 0;
uint32_t cr3_ = 0;

uint8_t rx_dr_ = 0; // received byte (single-slot buffer)

OutputCallback output_;
std::function<void()> irq_cb_;

WeakPtrFactory<Stm32f1Usart> weak_factory_{this};
};
Expand Down
8 changes: 7 additions & 1 deletion src/arch/arm/cortex_m3/cortex_m3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,13 @@ CPU::CPUExpected<addr_t> CortexM3CPU::set_pc(addr_t new_pc) {
return new_pc;
}

CPU::CPUExpected<void> CortexM3CPU::raise_irq(intr::intr_n_t) {
CPU::CPUExpected<void> CortexM3CPU::raise_irq(intr::intr_n_t irq) {
// External peripheral → NVIC pending injection. This is the single channel
// every MMIO IRQ (TIM/USART/EXTI…) funnels through; SysTick bypasses it via
// sys_tick_irq() (system exception 15, not an NVIC line).
if (nvic_) {
nvic_->set_pending(static_cast<uint8_t>(irq));
}
return {};
}

Expand Down
6 changes: 6 additions & 0 deletions src/chips/stm32f1/peripheral_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ Expected<void> configure_peripherals(memory::Bus& bus, Stm32f103Parts& parts) {
return result;
}

result = map_checked(0x4001'0400_addr, 0x400_addr,
parts.exti.GetWeak());
if (!result) {
return result;
}

return {};
}

Expand Down
Loading
Loading