summaryrefslogtreecommitdiffstats
path: root/ble/src/ble.rs
blob: ac3542ff9db18116bdab56d55ace64e4d8b5b6d5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use bbqueue::{bbq, BBQueue};
use core::{cell::RefCell, ops::DerefMut};
use cortex_m::{self, interrupt::Mutex};
use log::info;
use nrf52840_hal::target::{interrupt, FICR, RADIO, TIMER0};
use rubble::{
    gatt::BatteryServiceAttrs,
    l2cap::{BleChannelMap, L2CAPState},
    link::{
        ad_structure::AdStructure, queue, AddressKind, DeviceAddress, HardwareInterface, LinkLayer,
        Responder, MIN_PDU_BUF,
    },
    security_manager::NoSecurity,
    time::{Duration, Timer},
};
use rubble_nrf52::{
    radio::{BleRadio, PacketBuffer},
    timer::BleTimer,
};

struct NRF52840 {}
impl HardwareInterface for NRF52840 {
    type Timer = BleTimer<TIMER0>;
    type Tx = BleRadio;
}

type Global<T> = Mutex<RefCell<Option<T>>>;

static BLE_LL: Global<LinkLayer<NRF52840>> = Mutex::new(RefCell::new(None));
static BLE_RADIO: Global<BleRadio> = Mutex::new(RefCell::new(None));

pub fn setup(
    radio: RADIO,
    timer: TIMER0,
    ficr: FICR,
) -> Responder<BleChannelMap<BatteryServiceAttrs, NoSecurity>> {
    // make sure hfclock is started for timer0 to work at the right
    // resolution.
    let ble_timer = BleTimer::init(timer);
    let devaddr_lo: &[u8] = &ficr.deviceaddr[0].read().bits().to_le_bytes();
    let devaddr_hi: &[u8] = &(ficr.deviceaddr[1].read().bits() as u16).to_le_bytes();

    let mut devaddr: [u8; 6] = [0; 6];
    for (i, a) in [devaddr_lo, devaddr_hi]
        .iter()
        .copied()
        .flatten()
        .enumerate()
    {
        devaddr[i] = *a;
    }

    let addrkind = if ficr.deviceaddrtype.read().deviceaddrtype().is_public() {
        AddressKind::Public
    } else {
        AddressKind::Random
    };
    let addr = DeviceAddress::new(devaddr, addrkind);
    info!("my address: {:?}", addr);

    static mut TX_BUF: PacketBuffer = [0; MIN_PDU_BUF];
    static mut RX_BUF: PacketBuffer = [0; MIN_PDU_BUF];

    let (tx_prod, tx_cons) = queue::create(bbq![MIN_PDU_BUF * 2].expect("creating tx queue"));
    let (rx_prod, rx_cons) = queue::create(bbq![MIN_PDU_BUF * 2].expect("creating rx queue"));

    // `responder` used on main loop.
    let resp = Responder::new(
        tx_prod,
        rx_cons,
        L2CAPState::new(BleChannelMap::with_attributes(BatteryServiceAttrs::new())),
    );

    // `ble_radio` and `ll` used on both `timer` and `radio`
    // interrupts.
    // may be able to use ring buffer to avoid sharing?
    let mut ble_radio = unsafe { BleRadio::new(radio, &mut TX_BUF, &mut RX_BUF) };

    let mut ble_ll = LinkLayer::<NRF52840>::new(addr, ble_timer);
    let next_update = ble_ll
        .start_advertise(
            Duration::from_millis(200),
            &[AdStructure::CompleteLocalName("bleusb")],
            &mut ble_radio,
            tx_cons,
            rx_prod,
        )
        .expect("scheduling link layer update");
    ble_ll.timer().configure_interrupt(next_update);

    cortex_m::interrupt::free(|cs| {
        BLE_LL.borrow(cs).replace(Some(ble_ll));
        BLE_RADIO.borrow(cs).replace(Some(ble_radio));
    });

    resp
}

#[interrupt]
fn TIMER0() {
    cortex_m::interrupt::free(|cs| {
        if let (Some(ref mut ble_ll), Some(ref mut ble_radio)) = (
            BLE_LL.borrow(cs).borrow_mut().deref_mut(),
            BLE_RADIO.borrow(cs).borrow_mut().deref_mut(),
        ) {
            if !ble_ll.timer().is_interrupt_pending() {
                return;
            }
            ble_ll.timer().clear_interrupt();

            let cmd = ble_ll.update(ble_radio);
            ble_radio.configure_receiver(cmd.radio);

            ble_ll.timer().configure_interrupt(cmd.next_update);
        }
    })
}

#[interrupt]
fn RADIO() {
    cortex_m::interrupt::free(|cs| {
        if let (Some(ref mut ble_ll), Some(ref mut ble_radio)) = (
            BLE_LL.borrow(cs).borrow_mut().deref_mut(),
            BLE_RADIO.borrow(cs).borrow_mut().deref_mut(),
        ) {
            let next_update = ble_radio.recv_interrupt(ble_ll.timer().now(), ble_ll);
            ble_ll.timer().configure_interrupt(next_update);
        }
    })
}