Skip to content

Commit 9b60029

Browse files
committed
Improve I2C error handling and robustness
1 parent 8a1029c commit 9b60029

File tree

1 file changed

+78
-165
lines changed

1 file changed

+78
-165
lines changed

esp-hal-common/src/i2c.rs

Lines changed: 78 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -365,11 +365,13 @@ pub trait Instance {
365365
.ctr
366366
.modify(|_, w| w.conf_upgate().set_bit());
367367

368+
self.reset();
369+
368370
Ok(())
369371
}
370372

371373
/// Resets the I2C controller (FIFO + FSM + command list)
372-
fn reset(&mut self) {
374+
fn reset(&self) {
373375
// Reset interrupts
374376
// Disable all I2C interrupts
375377
self.register_block()
@@ -396,7 +398,7 @@ pub trait Instance {
396398
}
397399

398400
/// Resets the I2C peripheral's command registers
399-
fn reset_command_list(&mut self) {
401+
fn reset_command_list(&self) {
400402
// Confirm that all commands that were configured were actually executed
401403
for cmd in self.register_block().comd.iter() {
402404
cmd.reset();
@@ -632,7 +634,7 @@ pub trait Instance {
632634

633635
add_cmd(cmd_iterator, Command::Stop)?;
634636

635-
self.upgate_config();
637+
self.update_config();
636638

637639
// Load address and R/W bit into FIFO
638640
write_fifo(
@@ -645,7 +647,7 @@ pub trait Instance {
645647
self.start_transmission();
646648

647649
// fill FIFO with remaining bytes
648-
self.write_remaining_tx_fifo(index, bytes);
650+
self.write_remaining_tx_fifo(index, bytes)?;
649651

650652
self.wait_for_completion()?;
651653

@@ -704,7 +706,7 @@ pub trait Instance {
704706

705707
add_cmd(cmd_iterator, Command::Stop)?;
706708

707-
self.upgate_config();
709+
self.update_config();
708710

709711
// Load address and R/W bit into FIFO
710712
write_fifo(self.register_block(), addr << 1 | OperationType::Read as u8);
@@ -718,121 +720,18 @@ pub trait Instance {
718720
Ok(())
719721
}
720722

721-
#[cfg(not(debug_assertions))]
722-
fn perform_write_read<'a, I>(
723-
&self,
724-
addr: u8,
725-
bytes: &[u8],
726-
buffer: &mut [u8],
727-
cmd_iterator: &mut I,
728-
) -> Result<(), Error>
729-
where
730-
I: Iterator<Item = &'a COMD>,
731-
{
732-
if bytes.len() > 254 {
733-
// we could support more by adding multiple write operations
734-
return Err(Error::ExceedingFifo);
735-
}
736-
737-
// Clear all I2C interrupts
738-
self.clear_all_interrupts();
739-
740-
// RSTART command
741-
add_cmd(cmd_iterator, Command::Start)?;
742-
743-
// WRITE command
744-
add_cmd(
745-
cmd_iterator,
746-
Command::Write {
747-
ack_exp: Ack::Ack,
748-
ack_check_en: true,
749-
length: 1 + bytes.len() as u8,
750-
},
751-
)?;
752-
753-
add_cmd(cmd_iterator, Command::Start)?;
754-
755-
add_cmd(
756-
cmd_iterator,
757-
Command::Write {
758-
ack_exp: Ack::Ack,
759-
ack_check_en: true,
760-
length: 1,
761-
},
762-
)?;
763-
764-
if buffer.len() > 1 {
765-
// READ command (N - 1)
766-
add_cmd(
767-
cmd_iterator,
768-
Command::Read {
769-
ack_value: Ack::Ack,
770-
length: buffer.len() as u8 - 1,
771-
},
772-
)?;
773-
}
774-
775-
// READ w/o ACK
776-
add_cmd(
777-
cmd_iterator,
778-
Command::Read {
779-
ack_value: Ack::Nack,
780-
length: 1,
781-
},
782-
)?;
783-
784-
add_cmd(cmd_iterator, Command::Stop)?;
785-
786-
self.upgate_config();
787-
788-
// Load address and R/W bit into FIFO
789-
write_fifo(
790-
self.register_block(),
791-
addr << 1 | OperationType::Write as u8,
792-
);
793-
794-
let index = self.fill_tx_fifo(bytes);
795-
796-
self.start_transmission();
797-
798-
// fill FIFO with remaining bytes
799-
self.write_remaining_tx_fifo(index, bytes);
800-
801-
// writing to the FIFO here is too slow in debug mode apparently and the
802-
// address isn't loaded fast enough ... therefore in debug mode we do separate
803-
// write and reads
804-
self.write_remaining_tx_fifo(0, &[addr << 1 | OperationType::Read as u8]);
805-
806-
self.read_all_from_fifo(buffer)?;
807-
808-
self.wait_for_completion()?;
809-
810-
Ok(())
811-
}
812-
813723
#[cfg(not(any(esp32, esp32s2)))]
814724
fn read_all_from_fifo(&self, buffer: &mut [u8]) -> Result<(), Error> {
815725
// Read bytes from FIFO
816726
// FIXME: Handle case where less data has been provided by the slave than
817727
// requested? Or is this prevented from a protocol perspective?
818728
for byte in buffer.iter_mut() {
819729
loop {
820-
#[cfg(not(esp32))]
821-
{
822-
let reg = self.register_block().fifo_st.read();
823-
824-
if reg.rxfifo_raddr().bits() != reg.rxfifo_waddr().bits() {
825-
break;
826-
}
827-
}
828-
829-
#[cfg(esp32)]
830-
{
831-
let reg = self.register_block().rxfifo_st.read();
730+
self.check_errors()?;
832731

833-
if reg.rxfifo_start_addr().bits() != reg.rxfifo_end_addr().bits() {
834-
break;
835-
}
732+
let reg = self.register_block().fifo_st.read();
733+
if reg.rxfifo_raddr().bits() != reg.rxfifo_waddr().bits() {
734+
break;
836735
}
837736
}
838737

@@ -877,30 +776,7 @@ pub trait Instance {
877776
loop {
878777
let interrupts = self.register_block().int_raw.read();
879778

880-
// The ESP32 variant has a slightly different interrupt naming
881-
// scheme!
882-
cfg_if::cfg_if! {
883-
if #[cfg(esp32)] {
884-
// Handle error cases
885-
if interrupts.time_out_int_raw().bit_is_set() {
886-
return Err(Error::TimeOut);
887-
} else if interrupts.ack_err_int_raw().bit_is_set() {
888-
return Err(Error::AckCheckFailed);
889-
} else if interrupts.arbitration_lost_int_raw().bit_is_set() {
890-
return Err(Error::ArbitrationLost);
891-
}
892-
}
893-
else {
894-
// Handle error cases
895-
if interrupts.time_out_int_raw().bit_is_set() {
896-
return Err(Error::TimeOut);
897-
} else if interrupts.nack_int_raw().bit_is_set() {
898-
return Err(Error::AckCheckFailed);
899-
} else if interrupts.arbitration_lost_int_raw().bit_is_set() {
900-
return Err(Error::ArbitrationLost);
901-
}
902-
}
903-
}
779+
self.check_errors()?;
904780

905781
// Handle completion cases
906782
// A full transmission was completed
@@ -919,7 +795,43 @@ pub trait Instance {
919795
Ok(())
920796
}
921797

922-
fn upgate_config(&self) {
798+
fn check_errors(&self) -> Result<(), Error> {
799+
let interrupts = self.register_block().int_raw.read();
800+
801+
// The ESP32 variant has a slightly different interrupt naming
802+
// scheme!
803+
cfg_if::cfg_if! {
804+
if #[cfg(esp32)] {
805+
// Handle error cases
806+
if interrupts.time_out_int_raw().bit_is_set() {
807+
self.reset();
808+
return Err(Error::TimeOut);
809+
} else if interrupts.ack_err_int_raw().bit_is_set() {
810+
self.reset();
811+
return Err(Error::AckCheckFailed);
812+
} else if interrupts.arbitration_lost_int_raw().bit_is_set() {
813+
self.reset();
814+
return Err(Error::ArbitrationLost);
815+
}
816+
}
817+
else {
818+
// Handle error cases
819+
if interrupts.time_out_int_raw().bit_is_set() {
820+
self.reset();
821+
return Err(Error::TimeOut);
822+
} else if interrupts.nack_int_raw().bit_is_set() {
823+
self.reset();
824+
return Err(Error::AckCheckFailed);
825+
} else if interrupts.arbitration_lost_int_raw().bit_is_set() {
826+
return Err(Error::ArbitrationLost);
827+
}
828+
}
829+
}
830+
831+
Ok(())
832+
}
833+
834+
fn update_config(&self) {
923835
// Ensure that the configuration of the peripheral is correctly propagated
924836
// (only necessary for C3 and S3 variant)
925837
#[cfg(any(esp32c2, esp32c3, esp32s3))]
@@ -965,9 +877,11 @@ pub trait Instance {
965877
}
966878

967879
#[cfg(not(any(esp32, esp32s2)))]
968-
fn write_remaining_tx_fifo(&self, start_index: usize, bytes: &[u8]) {
880+
fn write_remaining_tx_fifo(&self, start_index: usize, bytes: &[u8]) -> Result<(), Error> {
969881
let mut index = start_index;
970882
loop {
883+
self.check_errors()?;
884+
971885
while !self
972886
.register_block()
973887
.int_raw
@@ -980,7 +894,7 @@ pub trait Instance {
980894
.write(|w| w.txfifo_wm_int_clr().set_bit());
981895

982896
if index >= bytes.len() {
983-
break;
897+
break Ok(());
984898
}
985899

986900
write_fifo(self.register_block(), bytes[index]);
@@ -1006,25 +920,28 @@ pub trait Instance {
1006920
}
1007921

1008922
#[cfg(any(esp32, esp32s2))]
1009-
fn write_remaining_tx_fifo(&self, start_index: usize, bytes: &[u8]) {
923+
fn write_remaining_tx_fifo(&self, start_index: usize, bytes: &[u8]) -> Result<(), Error> {
1010924
// on ESP32/ESP32-S2 we currently don't support I2C transactions larger than the
1011925
// FIFO apparently it would be possible by using non-fifo mode
1012926
// see https://github.com/espressif/arduino-esp32/blob/7e9afe8c5ed7b5bf29624a5cd6e07d431c027b97/cores/esp32/esp32-hal-i2c.c#L615
1013927

1014928
if start_index >= bytes.len() {
1015-
return;
929+
return Ok(());
1016930
}
1017931

1018932
// this is only possible when writing the I2C address in release mode
1019933
// from [perform_write_read]
1020934
for b in bytes {
1021935
write_fifo(self.register_block(), *b);
936+
self.check_errors()?;
1022937
}
938+
939+
Ok(())
1023940
}
1024941

1025942
/// Resets the transmit and receive FIFO buffers
1026943
#[cfg(not(esp32))]
1027-
fn reset_fifo(&mut self) {
944+
fn reset_fifo(&self) {
1028945
// First, reset the fifo buffers
1029946
self.register_block().fifo_conf.modify(|_, w| {
1030947
w.tx_fifo_rst()
@@ -1035,26 +952,29 @@ pub trait Instance {
1035952
.clear_bit()
1036953
.fifo_prt_en()
1037954
.set_bit()
1038-
.tx_fifo_rst()
1039-
.clear_bit()
1040-
.rx_fifo_rst()
1041-
.clear_bit()
1042955
.rxfifo_wm_thrhd()
1043956
.variant(1)
1044957
.txfifo_wm_thrhd()
1045958
.variant(8)
1046959
});
960+
961+
self.register_block()
962+
.fifo_conf
963+
.modify(|_, w| w.tx_fifo_rst().clear_bit().rx_fifo_rst().clear_bit());
964+
1047965
self.register_block().int_clr.write(|w| {
1048966
w.rxfifo_wm_int_clr()
1049967
.set_bit()
1050968
.txfifo_wm_int_clr()
1051969
.set_bit()
1052970
});
971+
972+
self.update_config();
1053973
}
1054974

1055975
/// Resets the transmit and receive FIFO buffers
1056976
#[cfg(esp32)]
1057-
fn reset_fifo(&mut self) {
977+
fn reset_fifo(&self) {
1058978
// First, reset the fifo buffers
1059979
self.register_block().fifo_conf.modify(|_, w| {
1060980
w.tx_fifo_rst()
@@ -1063,15 +983,16 @@ pub trait Instance {
1063983
.set_bit()
1064984
.nonfifo_en()
1065985
.clear_bit()
1066-
.tx_fifo_rst()
1067-
.clear_bit()
1068-
.rx_fifo_rst()
1069-
.clear_bit()
1070986
.nonfifo_rx_thres()
1071987
.variant(1)
1072988
.nonfifo_tx_thres()
1073989
.variant(32)
1074990
});
991+
992+
self.register_block()
993+
.fifo_conf
994+
.modify(|_, w| w.tx_fifo_rst().clear_bit().rx_fifo_rst().clear_bit());
995+
1075996
self.register_block()
1076997
.int_clr
1077998
.write(|w| w.rxfifo_full_int_clr().set_bit());
@@ -1106,20 +1027,12 @@ pub trait Instance {
11061027
bytes: &[u8],
11071028
buffer: &mut [u8],
11081029
) -> Result<(), Error> {
1109-
#[cfg(not(debug_assertions))]
1110-
{
1111-
// Reset FIFO and command list
1112-
self.reset_fifo();
1113-
self.reset_command_list();
1114-
1115-
self.perform_write_read(addr, bytes, buffer, &mut self.register_block().comd.iter())?;
1116-
}
1117-
1118-
#[cfg(debug_assertions)]
1119-
{
1120-
self.master_write(addr, bytes)?;
1121-
self.master_read(addr, buffer)?;
1122-
}
1030+
// it would be possible to combine the write and read
1031+
// in one transaction but filling the tx fifo with
1032+
// the current code is somewhat slow even in release mode
1033+
// which can cause issues
1034+
self.master_write(addr, bytes)?;
1035+
self.master_read(addr, buffer)?;
11231036
Ok(())
11241037
}
11251038
}

0 commit comments

Comments
 (0)