-
Notifications
You must be signed in to change notification settings - Fork 30
Expand file tree
/
Copy pathspi.rs
More file actions
173 lines (162 loc) · 5.67 KB
/
spi.rs
File metadata and controls
173 lines (162 loc) · 5.67 KB
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//! (TODO) Serial Peripheral Interface (SPI)
use crate::clock::Clocks;
use crate::pac::spi0::ctrlr0::TMOD_A as transfer_mode;
use crate::pac::SPI0;
use crate::sysctl::{self, APB0};
use core::convert::Infallible;
pub use embedded_hal::spi::{Mode, Phase, Polarity};
pub struct Spi<SPI> {
spi: SPI,
// Different with other MCUs like STM32 and Atmel
// k210 use fpioa to map a pin directly to SPI SS 0-3 instead of using an ordinary GPIO
// when transferring data, we'll set `pac::SPIX::ser` to (1 << cs_id) to select this device
cs_id: u8,
}
impl Spi<SPI0> {
pub fn spi0(
spi: SPI0,
cs_id: u8, // todo: currently we presume SPI0_SS<cs_id> is already configured correctly, maybe we can do this for the user?
mode: Mode,
frame_format: FrameFormat,
endian: Endian,
clock: &Clocks,
apb0: &mut APB0,
) -> Self {
let work_mode = hal_mode_to_pac(mode);
let frame_format = frame_format_to_pac(frame_format);
let endian = endian as u32;
let data_bit_length = 8; // todo more length
let _ = clock; // todo
unsafe {
// no interrupts for now
spi.imr.write(|w| w.bits(0x0));
// no dma for now
spi.dmacr.write(|w| w.bits(0x0));
spi.dmatdlr.write(|w| w.bits(0x10));
spi.dmardlr.write(|w| w.bits(0x0));
// no slave access for now
spi.ser.write(|w| w.bits(0x0));
spi.ssienr.write(|w| w.bits(0x0));
// set control registers
spi.ctrlr0.write(|w| {
// no need to set tmod here, which will (and needs to) be set on each send/recv
w.work_mode()
.variant(work_mode)
.frame_format()
.variant(frame_format)
.data_length()
.bits(data_bit_length - 1)
});
spi.spi_ctrlr0.reset(); // standard
spi.endian.write(|w| w.bits(endian));
}
// enable APB0 bus
apb0.enable();
// enable peripheral via sysctl
sysctl::clk_en_peri().modify(|_r, w| w.spi0_clk_en().set_bit());
Spi { spi, cs_id }
}
pub fn release(self) -> SPI0 {
// power off
sysctl::clk_en_peri().modify(|_r, w| w.spi0_clk_en().clear_bit());
self.spi
}
/// for making our life easier to use the same SPI interface but with different chip selected
pub fn take_for_cs(self, cs_id: u8) -> Self {
Self {
spi: self.spi,
cs_id,
}
}
}
// todo: Shall we make FrameFormat a type parameter instead?
// so FullDuplex<u8> can be implemented for Spi<SPI0, FrameFormat::Standard> only
impl embedded_hal::spi::FullDuplex<u8> for Spi<SPI0> {
/// An enumeration of SPI errors
type Error = Infallible;
/// Reads the word stored in the shift register
///
/// **NOTE** A word must be sent to the slave before attempting to call this
/// method.
fn try_read(&mut self) -> nb::Result<u8, Self::Error> {
self.spi
.ctrlr0
.modify(|_, w| w.tmod().variant(transfer_mode::RECV));
unsafe {
// C sdk said ctrlr1 = rx_len(1) / frame_width(1) - 1;
self.spi.ctrlr1.write(|w| w.bits(0x0));
// enable spi
self.spi.ssienr.write(|w| w.bits(0x1));
// select that chip
self.spi.ser.write(|w| w.bits(0x1 << self.cs_id));
// clear dr
self.spi.dr[0].write(|w| w.bits(0xffffffff));
}
let bytes_in_buffer = self.spi.rxflr.read().bits();
let result = if bytes_in_buffer == 0 {
Err(nb::Error::WouldBlock)
} else {
Ok(self.spi.dr[0].read().bits() as u8)
};
self.spi.ser.reset();
self.spi.ssienr.reset();
result
}
/// Sends a word to the slave
fn try_send(&mut self, word: u8) -> nb::Result<(), Self::Error> {
self.spi
.ctrlr0
.modify(|_, w| w.tmod().variant(transfer_mode::TRANS));
unsafe {
self.spi.ssienr.write(|w| w.bits(0x0));
self.spi.ser.write(|w| w.bits(0x1 << self.cs_id));
}
const MAX_FIFO_SIZE: u32 = 32;
let empty_in_buffer = MAX_FIFO_SIZE - self.spi.txflr.read().bits();
let result = if empty_in_buffer == 0 {
Err(nb::Error::WouldBlock)
} else {
unsafe {
self.spi.dr[0].write(|w| w.bits(word as u32));
}
Ok(())
};
self.spi.ser.reset();
self.spi.ssienr.reset();
result
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum FrameFormat {
Standard,
Dual,
Quad,
Octal,
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum Endian {
Little = 0,
Big = 1,
}
#[inline]
fn hal_mode_to_pac(mode: Mode) -> crate::pac::spi0::ctrlr0::WORK_MODE_A {
use crate::pac::spi0::ctrlr0::WORK_MODE_A;
use {Phase::*, Polarity::*};
match (mode.polarity, mode.phase) {
(IdleLow, CaptureOnFirstTransition) => WORK_MODE_A::MODE0,
(IdleLow, CaptureOnSecondTransition) => WORK_MODE_A::MODE1,
(IdleHigh, CaptureOnFirstTransition) => WORK_MODE_A::MODE2,
(IdleHigh, CaptureOnSecondTransition) => WORK_MODE_A::MODE3,
}
}
#[inline]
fn frame_format_to_pac(frame_format: FrameFormat) -> crate::pac::spi0::ctrlr0::FRAME_FORMAT_A {
use crate::pac::spi0::ctrlr0::FRAME_FORMAT_A;
match frame_format {
FrameFormat::Standard => FRAME_FORMAT_A::STANDARD,
FrameFormat::Dual => FRAME_FORMAT_A::DUAL,
FrameFormat::Quad => FRAME_FORMAT_A::QUAD,
FrameFormat::Octal => FRAME_FORMAT_A::OCTAL,
}
}