Skip to content

Commit 4ed5aa2

Browse files
authored
Merge pull request #18 from Neotron-Compute/kb_support
Nicer KB support
2 parents 5daab81 + 7acbc1d commit 4ed5aa2

File tree

4 files changed

+123
-38
lines changed

4 files changed

+123
-38
lines changed

Cargo.lock

Lines changed: 1 addition & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ panic-probe = { version = "0.2.0", features = ["print-defmt"] }
1919
# Use this line if you have an STM32F031K6T6
2020
stm32f0xx-hal = { version = "0.17", features = ["stm32f031", "rt"] }
2121
debouncr = "0.2"
22-
pc-keyboard = "0.5"
22+
heapless= "0.6"
2323

2424
[features]
2525
# set logging levels here

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ The NBMC firmware is designed to run on an ST Micro STM32F0 (STM32F031K6T6) micr
5757
| 29 | PB6 | I2C1_SCL | I²C Clock |
5858
| 30 | PB7 | I2C1_SDA | I²C Data |
5959

60-
Note that in the above table, the UART signals are wired as _Data Terminal Equipment (DTE)_ (i.e. like a PC, not like a Modem).
60+
Note that in the above table, the UART signals are wired as _Data Terminal Equipment (DTE)_ (i.e. like a PC, not like a Modem). Connect the NMBC *UART Transmit Output* pin to the *Input* pin of something like an FTDI TTL-232R-3V3 cable.
6161

6262
This design should also be pin-compatible with the following SoCs (although this firmware may need changes):
6363

src/main.rs

Lines changed: 120 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
///! # Licence
1010
///! This source code as a whole is licensed under the GPL v3. Third-party crates are covered by their respective licences.
1111
use cortex_m::interrupt::free as disable_interrupts;
12+
use heapless::{
13+
consts::*,
14+
i,
15+
spsc::{Consumer, Producer, Queue},
16+
};
1217
use rtic::app;
1318
use stm32f0xx_hal::{
1419
gpio::gpioa::{PA10, PA11, PA12, PA15, PA2, PA3, PA9},
@@ -21,7 +26,7 @@ use stm32f0xx_hal::{
2126
};
2227

2328
use neotron_bmc as _;
24-
use neotron_bmc::monotonic::{Instant, Tim3Monotonic, U16Ext};
29+
use neotron_bmc::monotonic::{Tim3Monotonic, U16Ext};
2530

2631
/// Version string auto-generated by git.
2732
static VERSION: &'static str = include_str!(concat!(env!("OUT_DIR"), "/version.txt"));
@@ -44,6 +49,26 @@ pub enum DcPowerState {
4449
Off = 0,
4550
}
4651

52+
/// Handles decoding incoming PS/2 packets
53+
///
54+
/// Each packet has 11 bits:
55+
///
56+
/// * Start Bit
57+
/// * 8 Data Bits (LSB first)
58+
/// * Parity Bit
59+
/// * Stop Bit
60+
#[derive(Debug)]
61+
pub struct Ps2Decoder {
62+
bit_count: u8,
63+
collector: u16,
64+
}
65+
66+
/// This is our system state, as accessible via SPI reads and writes.
67+
#[derive(Debug)]
68+
pub struct RegisterState {
69+
firmware_version: &'static str,
70+
}
71+
4772
#[app(device = crate::pac, peripherals = true, monotonic = crate::Tim3Monotonic)]
4873
const APP: () = {
4974
struct Resources {
@@ -82,8 +107,16 @@ const APP: () = {
82107
ps2_dat1: PB5<Input<Floating>>,
83108
/// The external interrupt peripheral
84109
exti: pac::EXTI,
85-
/// Our PS/2 keyboard decoder
86-
kb: pc_keyboard::Keyboard<pc_keyboard::layouts::Uk105Key, pc_keyboard::ScancodeSet2>,
110+
/// Our register state
111+
register_state: RegisterState,
112+
/// Keyboard PS/2 decoder
113+
kb_decoder: Ps2Decoder,
114+
/// Mouse PS/2 decoder
115+
ms_decoder: Ps2Decoder,
116+
/// Keyboard bytes sink
117+
kb_c: Consumer<'static, u16, U8>,
118+
/// Keyboard bytes source
119+
kb_p: Producer<'static, u16, U8>,
87120
}
88121

89122
/// The entry point to our application.
@@ -94,6 +127,8 @@ const APP: () = {
94127
/// * Task `button_poll` - checks the power and reset buttons
95128
#[init(spawn = [led_power_blink, button_poll])]
96129
fn init(ctx: init::Context) -> init::LateResources {
130+
static mut Q: Queue<u16, U8> = Queue(i::Queue::new());
131+
97132
defmt::info!("Neotron BMC version {:?} booting", VERSION);
98133

99134
let dp: pac::Peripherals = ctx.device;
@@ -174,6 +209,8 @@ const APP: () = {
174209

175210
defmt::info!("Init complete!");
176211

212+
let (kb_p, kb_c) = Q.split();
213+
177214
init::LateResources {
178215
serial,
179216
pin_uart_cts,
@@ -192,48 +229,49 @@ const APP: () = {
192229
ps2_dat0,
193230
ps2_dat1,
194231
exti: dp.EXTI,
195-
kb: pc_keyboard::Keyboard::new(
196-
pc_keyboard::layouts::Uk105Key,
197-
pc_keyboard::ScancodeSet2,
198-
pc_keyboard::HandleControl::MapLettersToUnicode,
199-
),
232+
register_state: RegisterState {
233+
firmware_version: "Neotron BMC v0.0.0",
234+
},
235+
kb_p,
236+
kb_c,
237+
kb_decoder: Ps2Decoder::new(),
238+
ms_decoder: Ps2Decoder::new(),
200239
}
201240
}
202241

203242
/// Our idle task.
204243
///
205244
/// This task is called when there is nothing else to do. We
206245
/// do a little logging, then put the CPU to sleep waiting for an interrupt.
207-
#[idle(resources = [])]
208-
fn idle(_ctx: idle::Context) -> ! {
246+
#[idle(resources = [kb_c])]
247+
fn idle(ctx: idle::Context) -> ! {
209248
defmt::info!("Idle is running...");
210249
loop {
211-
cortex_m::asm::wfi();
212-
defmt::trace!("It is now {}", crate::Instant::now().counts());
250+
if let Some(word) = ctx.resources.kb_c.dequeue() {
251+
if let Some(byte) = Ps2Decoder::check_word(word) {
252+
defmt::info!("< KB {:x}", byte);
253+
} else {
254+
defmt::info!("< Bad KB {:x}", word);
255+
}
256+
}
213257
}
214258
}
215259

216260
/// This is the PS/2 Keyboard task.
217261
///
262+
/// It is very high priority, as we can't afford to miss a clock edge.
263+
///
218264
/// It fires when there is a falling edge on the PS/2 Keyboard clock pin.
219-
#[task(binds = EXTI4_15, resources=[ps2_clk0, ps2_dat0, exti, kb])]
265+
#[task(
266+
binds = EXTI4_15,
267+
priority = 15,
268+
resources=[ps2_clk0, ps2_dat0, exti, kb_decoder, kb_p])]
220269
fn exti4_15_interrupt(ctx: exti4_15_interrupt::Context) {
221-
match ctx
222-
.resources
223-
.kb
224-
.add_bit(ctx.resources.ps2_dat0.is_high().unwrap())
225-
{
226-
Err(e) => {
227-
defmt::warn!("Error decoding kb: {}", e as usize);
228-
}
229-
Ok(None) => {}
230-
Ok(Some(evt)) => {
231-
defmt::info!(
232-
"Got event core={}, state={}",
233-
evt.code as usize,
234-
evt.state as usize
235-
);
236-
}
270+
let data_bit = ctx.resources.ps2_dat0.is_high().unwrap();
271+
// Do we have a complete word (and if so, is the parity OK)?
272+
if let Some(data) = ctx.resources.kb_decoder.add_bit(data_bit) {
273+
// Don't dump in the ISR - we're busy. Add it to this nice lockless queue instead.
274+
ctx.resources.kb_p.enqueue(data).unwrap();
237275
}
238276
// Clear the pending flag
239277
ctx.resources.exti.pr.write(|w| w.pr15().set_bit());
@@ -347,6 +385,59 @@ const APP: () = {
347385
}
348386
};
349387

388+
impl Ps2Decoder {
389+
fn new() -> Ps2Decoder {
390+
Ps2Decoder {
391+
bit_count: 0,
392+
collector: 0,
393+
}
394+
}
395+
396+
fn reset(&mut self) {
397+
self.bit_count = 0;
398+
self.collector = 0;
399+
}
400+
401+
fn add_bit(&mut self, bit: bool) -> Option<u16> {
402+
if bit {
403+
self.collector |= 1 << self.bit_count;
404+
}
405+
self.bit_count += 1;
406+
if self.bit_count == 11 {
407+
let result = self.collector;
408+
self.reset();
409+
Some(result)
410+
} else {
411+
None
412+
}
413+
}
414+
415+
/// Check 11-bit word has 1 start bit, 1 stop bit and an odd parity bit.
416+
fn check_word(word: u16) -> Option<u8> {
417+
let start_bit = (word & 0x0001) != 0;
418+
let parity_bit = (word & 0x0200) != 0;
419+
let stop_bit = (word & 0x0400) != 0;
420+
let data = ((word >> 1) & 0xFF) as u8;
421+
422+
if start_bit {
423+
return None;
424+
}
425+
426+
if !stop_bit {
427+
return None;
428+
}
429+
430+
let need_parity = (data.count_ones() % 2) == 0;
431+
432+
// Odd parity, so these must not match
433+
if need_parity != parity_bit {
434+
return None;
435+
}
436+
437+
Some(data)
438+
}
439+
}
440+
350441
// TODO: Pins we haven't used yet
351442
// SPI pins
352443
// spi_clk: gpioa.pa5.into_alternate_af0(cs),

0 commit comments

Comments
 (0)