Objective: implement a single byte I²C slave on STM32.
Materials: STM32L452RE Nucleo-64 board, HAL Library, CH341 USB-I²C adapter(1), Artix Linux with Runit, Rhode & Schwarz RTB2004 oscilloscope
Question
How can I control the slave ACK signal?
As I will present, ACK is sometimes sent, sometimes not sent. My guess would be with I2C_FIRST_FRAME
, I2C_NEXT_FRAME
and I2C_LAST_FRAME
- but they don't seem to have any effect!
Minimal example
The following non-functional example should:
- receive
cnt
number andcnt
bytes, storing them in a buffer; - transmit
cnt
number andcnt
bytes; - keep updating UART output with current stored buffer.
7-bit slave address is 0x6E
.
void HAL_I2C_ListenCpltCallback(I2C_HandleTypeDef* hi2c) {
HAL_I2C_EnableListen_IT(hi2c);
}
void HAL_I2C_AddrCallback(I2C_HandleTypeDef* hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode) {
if (TransferDirection == I2C_DIRECTION_TRANSMIT) {
HAL_I2C_Slave_Seq_Receive_DMA(hi2c, &cnt, 1, I2C_FIRST_FRAME);
HAL_I2C_Slave_Seq_Receive_DMA(hi2c, buffer, cnt, I2C_LAST_FRAME);
} else {
HAL_I2C_Slave_Seq_Transmit_DMA(hi2c, &cnt, 1, I2C_FIRST_FRAME);
HAL_I2C_Slave_Seq_Transmit_DMA(hi2c, buffer, cnt, I2C_LAST_FRAME);
}
}
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef* hi2c) {
}
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef* hi2c) {
}
void HAL_I2C_ErrorCallback(I2C_HandleTypeDef* hi2c) {
}
void buffer_print(const uint8_t buf[], const char* name, const size_t size) {
printf("%-20s[ ", name);
for (uint8_t i = 0; i < size; ++i)
printf("0x%04X ", i);
printf("]\r\n ");
for (uint8_t i = 0; i < size; ++i)
printf(" 0x%02X ", buf[i]);
printf("\r\n");
}
#define BUFFER_PRINT(buffer, size) buffer_print(buffer, #buffer, size)
int main() {
//...
HAL_I2C_EnableListen_IT(&hi2c3);
while (1) {
BUFFER_PRINT(buffer, cnt);
HAL_Delay(100);
}
}
How am I testing: with the following shell script:
#!/bin/bash -e
bus=$(i2cdetect -l | awk '/ch341/ {gsub("i2c-","",$1); print $1}')
dev="6e"
echo "Bus is ${bus}"
[ ! -c "/dev/i2c-${bus}" ] && echo "Bus /dev/i2c-${bus} not found" && exit
i2cdetect -y ${bus}
if ! i2cdetect -y ${bus} | grep ${dev} >/dev/null; then
echo "Device not found"
exit
fi
dev="0x${dev}"
size=10
echo "Writing ${size} consecutive bytes"
i2ctransfer -y ${bus} w$(( size + 1 ))@${dev} ${size} 0x01+
echo "Reading bytes"
i2ctransfer -y ${bus} w1@${dev} ${size} r${size}
Testing write operation
- No stretch
- Analog filter does not have an effect
write operation
- With
HAL_I2C_Slave_Seq_Receive_DMA()
, received buffer will show:
buffer [ 0x0000 ] <-- wrong cnt
0x01 <-- correct value
- With
HAL_I2C_Slave_Seq_Receive_IT()
, received buffer will show:
buffer [ 0x0000 0x0001 0x0002 0x0003 0x0004 0x0005 0x0006 0x0007 0x0008 0x0009 ] <-- correct cnt
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 <-- wrong values
ACK is randomly not sent.
detail on write operation: ACK and NACK signs
ACK looks like this:
detail on ACK sign
NACK looks the same like ACK:
detail on NACK sign
Sometimes, no ACK will be sent:
write operation without ACK
Independent of ACK and NACK, reported buffer
will be the same.
Thank you!
(1): Used driver