Skip to content

Commit 0ea1565

Browse files
committed
feat(hal): implement I2C instance
- Add new example `pbp-hello-world`. - Fix code and doc issues. Signed-off-by: Chongbing Yu <nanahigh@openatom.club>
1 parent 5e09a39 commit 0ea1565

26 files changed

Lines changed: 673 additions & 39 deletions

File tree

.github/workflows/Cargo.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ jobs:
101101
strategy:
102102
matrix:
103103
FEATURES: [d13x]
104-
EXAMPLES: [pbp-blinky, pbp-hello-world]
104+
EXAMPLES: [pbp-blinky, pbp-hello-world, pbp-i2c-master]
105105
steps:
106106
- uses: actions/checkout@v4
107107
- uses: actions-rust-lang/setup-rust-toolchain@v1

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ members = [
55
"aicfwc",
66
"examples/pbp/pbp-blinky",
77
"examples/pbp/pbp-hello-world",
8+
"examples/pbp/pbp-i2c-master",
89
]
910
resolver = "3"

artinchip-hal/src/gtc/delay.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ impl<'a> TimerDelay<'a> {
1414
let clk = &cmu.clock_gtc;
1515
unsafe {
1616
// Initialize module clock.
17-
// Reference: https://github.com/artinchip/luban-lite/blob/77fbe30dd2de366fae702ece84e1694153d94591/bsp/artinchip/hal/cmu/aic_hal_multi_parent_clk.c#L15
17+
// Reference: https://aicdoc.artinchip.com/topics/ic/cmu/cmu-function2-d13x.html#topic_yvp_f24_4bc__table_qb3_bn5_ydc
1818
clk.modify(|v| v.enable_bus_clk());
1919
clk.modify(|v| v.enable_module_reset());
2020
riscv::asm::delay(500);

artinchip-hal/src/gtc/instance.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ impl Gtc {
2222
}
2323

2424
/// Get a reference to the register block.
25-
pub const fn register_block(&self) -> &RegisterBlock {
25+
pub const fn register_block(&self) -> &'static RegisterBlock {
2626
unsafe { &*self.reg }
2727
}
2828
}
2929

3030
impl GtcExt<'static> for Gtc {
3131
fn new_timer_delay(self, freq: CntFreq, cmu: &cmu::RegisterBlock) -> TimerDelay<'static> {
32-
TimerDelay::new(unsafe { &*self.reg }, freq, cmu)
32+
TimerDelay::new(self.register_block(), freq, cmu)
3333
}
3434
}

artinchip-hal/src/i2c.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
//! Inter-Integrated Circuit (I2C).
22
3+
mod blocking;
4+
mod config;
5+
mod i2c_ext;
6+
mod instance;
7+
mod pad;
8+
mod pin_mux;
39
mod register;
410

11+
pub use blocking::*;
12+
pub use config::*;
13+
pub use i2c_ext::I2cExt;
14+
pub use instance::I2c;
515
pub use register::*;

artinchip-hal/src/i2c/blocking.rs

Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
//! Blocking I2C interface.
2+
3+
use embedded_hal::i2c::{Operation, SevenBitAddress, TenBitAddress};
4+
5+
use super::config::{I2cConfig, Role};
6+
use super::instance::I2c;
7+
use super::pad::{I2cPad, SerialClock, SerialData};
8+
use super::register::{AddressMode, RegisterBlock, SpeedMode, TransferMode};
9+
use crate::cmu;
10+
11+
/// Blocking I2C interface.
12+
pub struct BlockingI2c<'a, const I: u8, SCL, SDA>
13+
where
14+
SCL: I2cPad<I> + SerialClock<I>,
15+
SDA: I2cPad<I> + SerialData<I>,
16+
{
17+
reg: &'a RegisterBlock,
18+
scl: SCL,
19+
sda: SDA,
20+
}
21+
22+
impl<'a, const I: u8, SCL, SDA> BlockingI2c<'a, I, SCL, SDA>
23+
where
24+
SCL: I2cPad<I> + SerialClock<I>,
25+
SDA: I2cPad<I> + SerialData<I>,
26+
{
27+
// I2C fixed clock is 24Mhz.
28+
const I2C_DEFAULT_CLOCK: u32 = 24_000_000;
29+
30+
/// Create a new blocking serial.
31+
pub fn new(
32+
reg: &'a RegisterBlock,
33+
scl: SCL,
34+
sda: SDA,
35+
config: I2cConfig,
36+
clk: &cmu::RegisterBlock,
37+
) -> Self {
38+
// Reference: https://aicdoc.artinchip.com/topics/ic/i2c/i2c-programming-guide-d13x.html
39+
let i2c_clk = match I {
40+
0 => &clk.clock_i2c0,
41+
1 => &clk.clock_i2c1,
42+
2 => &clk.clock_i2c2,
43+
3 => &clk.clock_i2c3,
44+
_ => panic!("Invalid I2C index"),
45+
};
46+
unsafe {
47+
// Initialize module clock.
48+
// Reference: https://aicdoc.artinchip.com/topics/ic/cmu/cmu-function2-d13x.html#topic_yvp_f24_4bc__table_qb3_bn5_ydc
49+
i2c_clk.modify(|v| v.enable_bus_clk());
50+
i2c_clk.modify(|v| v.enable_module_reset());
51+
riscv::asm::delay(500);
52+
i2c_clk.modify(|v| v.disable_module_reset());
53+
54+
// Disable I2C module before configuration.
55+
reg.enable.modify(|v| v.disable_i2c());
56+
57+
// Disable interrupts.
58+
reg.intr_mask.modify(|v| v.disable_all());
59+
60+
// Configure SCL high and low counts.
61+
let (scl_hcnt, scl_lcnt) = Self::calc_scl_cnt(config.speed_mode);
62+
match config.speed_mode {
63+
SpeedMode::Standard => {
64+
reg.ss_scl_hcnt.modify(|v| v.set_scl_high_count(scl_hcnt));
65+
reg.ss_scl_lcnt.modify(|v| v.set_scl_low_count(scl_lcnt));
66+
}
67+
SpeedMode::Fast => {
68+
reg.fs_scl_hcnt.modify(|v| v.set_scl_high_count(scl_hcnt));
69+
reg.fs_scl_lcnt.modify(|v| v.set_scl_low_count(scl_lcnt));
70+
}
71+
}
72+
73+
// Configure SDA hold time.
74+
reg.sda_hold
75+
.modify(|v| v.set_sda_tx_hold(10).set_sda_rx_hold(0));
76+
77+
// Configure I2C role.
78+
match config.role {
79+
Role::Master => reg.ctrl.modify(|v| {
80+
v.enable_master_mode()
81+
.disable_slave_mode()
82+
.set_speed_mode(config.speed_mode)
83+
.enable_restart()
84+
}),
85+
Role::Slave => reg.ctrl.modify(|v| {
86+
v.enable_slave_mode()
87+
.disable_master_mode()
88+
.set_speed_mode(config.speed_mode)
89+
.set_stop_detect_if_addressed(true)
90+
}),
91+
}
92+
93+
// Enable I2C.
94+
reg.enable.modify(|v| v.enable_i2c());
95+
}
96+
97+
Self { reg, scl, sda }
98+
}
99+
100+
/// Set address.
101+
pub fn set_address(&self, address: u16) {
102+
// Disable I2C.
103+
unsafe {
104+
self.reg.enable.modify(|v| v.disable_i2c());
105+
}
106+
107+
let addr_mode = if address > 0x7F {
108+
AddressMode::Bit10
109+
} else {
110+
AddressMode::Bit7
111+
};
112+
unsafe {
113+
if self.reg.ctrl.read().is_master_mode_enabled() {
114+
self.reg
115+
.ctrl
116+
.modify(|v| v.set_address_mode_master(addr_mode));
117+
self.reg.target.modify(|v| v.set_target_address(address));
118+
} else {
119+
self.reg
120+
.ctrl
121+
.modify(|v| v.set_address_mode_slave(addr_mode));
122+
self.reg.slave_addr.modify(|v| v.set_slave_address(address));
123+
}
124+
}
125+
126+
// Enable I2C.
127+
unsafe {
128+
self.reg.enable.modify(|v| v.enable_i2c());
129+
}
130+
}
131+
132+
/// Calculate SCL high and low counts.
133+
///
134+
/// Standard mode: tHIGH_min = 4000ns, tLOW_min = 4700ns.
135+
/// Fast mode: tHIGH_min = 600ns, tLOW_min = 1300ns.
136+
#[inline]
137+
fn calc_scl_cnt(speed: SpeedMode) -> (u16, u16) {
138+
// Reference: https://github.com/artinchip/luban-lite/blob/main/bsp/artinchip/hal/i2c/hal_i2c.c#L83
139+
const SS_MIN_SCL_HIGH_NS: u32 = 4000; // Standard mode tHIGH min.
140+
const SS_MIN_SCL_LOW_NS: u32 = 4700; // Standard mode tLOW min.
141+
const FS_MIN_SCL_HIGH_NS: u32 = 600; // Fast mode tHIGH min.
142+
const FS_MIN_SCL_LOW_NS: u32 = 1300; // Fast mode tLOW min.
143+
const MARGIN: u16 = 2; // Safety margin.
144+
145+
let (high_ns, low_ns) = match speed {
146+
SpeedMode::Standard => (SS_MIN_SCL_HIGH_NS, SS_MIN_SCL_LOW_NS),
147+
SpeedMode::Fast => (FS_MIN_SCL_HIGH_NS, FS_MIN_SCL_LOW_NS),
148+
};
149+
150+
// Calculate counts: count = (time_ns * clk_freq_khz) / 1_000_000.
151+
// Divide clk_freq by 1000 first to prevent overflow.
152+
let clk_khz = Self::I2C_DEFAULT_CLOCK / 1000;
153+
let hcnt = ((high_ns * clk_khz) / 1_000_000) as u16 + MARGIN;
154+
let lcnt = ((low_ns * clk_khz) / 1_000_000) as u16 + MARGIN;
155+
156+
(hcnt, lcnt)
157+
}
158+
159+
/// Free the blocking I2C and return I2C instance, SCL and SDA pads.
160+
pub fn free(self, clk: &cmu::RegisterBlock) -> (I2c<I>, SCL, SDA) {
161+
unsafe {
162+
let i2c_clk = match I {
163+
0 => &clk.clock_i2c0,
164+
1 => &clk.clock_i2c1,
165+
2 => &clk.clock_i2c2,
166+
_ => &clk.clock_i2c3,
167+
};
168+
i2c_clk.modify(|v| v.disable_bus_clk().enable_module_reset());
169+
}
170+
(I2c::__new(self.reg), self.scl, self.sda)
171+
}
172+
}
173+
174+
impl<'a, const I: u8, SCL, SDA> embedded_hal::i2c::ErrorType for BlockingI2c<'a, I, SCL, SDA>
175+
where
176+
SCL: I2cPad<I> + SerialClock<I>,
177+
SDA: I2cPad<I> + SerialData<I>,
178+
{
179+
type Error = core::convert::Infallible;
180+
}
181+
182+
impl<'a, const I: u8, SCL, SDA> embedded_hal::i2c::I2c<SevenBitAddress>
183+
for BlockingI2c<'a, I, SCL, SDA>
184+
where
185+
SCL: I2cPad<I> + SerialClock<I>,
186+
SDA: I2cPad<I> + SerialData<I>,
187+
{
188+
fn transaction(
189+
&mut self,
190+
address: u8,
191+
operations: &mut [Operation<'_>],
192+
) -> Result<(), Self::Error> {
193+
self.set_address(address as u16);
194+
195+
let total_ops = operations.len();
196+
197+
for (idx, operation) in operations.iter_mut().enumerate() {
198+
let is_last_op = idx == total_ops - 1;
199+
200+
match operation {
201+
Operation::Write(bytes) => {
202+
for (byte_idx, &byte) in bytes.iter().enumerate() {
203+
while !self.reg.status.read().is_tx_fifo_not_full() {
204+
core::hint::spin_loop();
205+
}
206+
207+
let is_last_byte = byte_idx == bytes.len() - 1;
208+
// Only send STOP on last byte of last operation
209+
let send_stop = is_last_op && is_last_byte;
210+
211+
unsafe {
212+
// I2C_DATA_CMD[8]=0 (write), [7:0]=data, [9]=STOP
213+
self.reg.data_cmd.modify(|v| {
214+
v.set_data_byte(byte)
215+
.set_transfer_mode(TransferMode::Write)
216+
.set_stop(send_stop)
217+
});
218+
}
219+
}
220+
}
221+
Operation::Read(buffer) => {
222+
let len = buffer.len();
223+
224+
// Issue read commands
225+
for byte_idx in 0..len {
226+
while !self.reg.status.read().is_tx_fifo_not_full() {
227+
core::hint::spin_loop();
228+
}
229+
230+
let is_last_byte = byte_idx == len - 1;
231+
let send_stop = is_last_op && is_last_byte;
232+
233+
unsafe {
234+
// I2C_DATA_CMD[8]=1 (read), [9]=STOP
235+
self.reg.data_cmd.modify(|v| {
236+
v.set_transfer_mode(TransferMode::Read).set_stop(send_stop)
237+
});
238+
}
239+
}
240+
241+
// Read data from RX FIFO
242+
for byte in buffer.iter_mut() {
243+
while self.reg.rx_flr.read().rx_fifo_count() == 0 {
244+
core::hint::spin_loop();
245+
}
246+
*byte = self.reg.data_cmd.read().data_byte();
247+
}
248+
}
249+
}
250+
}
251+
252+
Ok(())
253+
}
254+
}
255+
256+
impl<'a, const I: u8, SCL, SDA> embedded_hal::i2c::I2c<TenBitAddress>
257+
for BlockingI2c<'a, I, SCL, SDA>
258+
where
259+
SCL: I2cPad<I> + SerialClock<I>,
260+
SDA: I2cPad<I> + SerialData<I>,
261+
{
262+
fn transaction(
263+
&mut self,
264+
address: u16,
265+
operations: &mut [Operation<'_>],
266+
) -> Result<(), Self::Error> {
267+
self.set_address(address);
268+
269+
let total_ops = operations.len();
270+
271+
for (idx, operation) in operations.iter_mut().enumerate() {
272+
let is_last_op = idx == total_ops - 1;
273+
274+
match operation {
275+
Operation::Write(bytes) => {
276+
for (byte_idx, &byte) in bytes.iter().enumerate() {
277+
while !self.reg.status.read().is_tx_fifo_not_full() {
278+
core::hint::spin_loop();
279+
}
280+
281+
let is_last_byte = byte_idx == bytes.len() - 1;
282+
// Only send STOP on last byte of last operation
283+
let send_stop = is_last_op && is_last_byte;
284+
285+
unsafe {
286+
// I2C_DATA_CMD[8]=0 (write), [7:0]=data, [9]=STOP
287+
self.reg.data_cmd.modify(|v| {
288+
v.set_data_byte(byte)
289+
.set_transfer_mode(TransferMode::Write)
290+
.set_stop(send_stop)
291+
});
292+
}
293+
}
294+
}
295+
Operation::Read(buffer) => {
296+
let len = buffer.len();
297+
298+
// Issue read commands
299+
for byte_idx in 0..len {
300+
while !self.reg.status.read().is_tx_fifo_not_full() {
301+
core::hint::spin_loop();
302+
}
303+
304+
let is_last_byte = byte_idx == len - 1;
305+
let send_stop = is_last_op && is_last_byte;
306+
307+
unsafe {
308+
// I2C_DATA_CMD[8]=1 (read), [9]=STOP
309+
self.reg.data_cmd.modify(|v| {
310+
v.set_transfer_mode(TransferMode::Read).set_stop(send_stop)
311+
});
312+
}
313+
}
314+
315+
// Read data from RX FIFO
316+
for byte in buffer.iter_mut() {
317+
while self.reg.rx_flr.read().rx_fifo_count() == 0 {
318+
core::hint::spin_loop();
319+
}
320+
*byte = self.reg.data_cmd.read().data_byte();
321+
}
322+
}
323+
}
324+
}
325+
326+
Ok(())
327+
}
328+
}

0 commit comments

Comments
 (0)