STM32 IIC从机的使用注意点,解决IIC发送第一个字节的数据错误

**

STM32 IIC从机的使用注意点,解决IIC发送第一个字节的数据错误

**

相关中断标志位

使用STM32 IIC作为从机,不可避免的就要用到中断对接收和发送数据进行处理。
下面讲下常用的中断标志,这里以STM32F072RB为例,在IIC的中断寄存器IIC-ISR有如下标志位
ADDR: 当主机发送地址,作为从机的STM32匹配上了地址,该位会置位为1,同时如果此时开了IIC的中断就会进入中断函数。
RXNE:当主机是发送数据给从机的,则每一次的接收数据寄存器有一个字节的数据时,该位会置1。如果此时开了IIC的中断就会进入中断函数。当从接收寄存器读取数据后,该位会由硬件清0。
TXE:当主机是接收数据的一方,作为从机的STM32在发送数据寄存器为空的时候。该位会置1,如果此时开了IIC的中断就会进入中断函数。一般我们会在中断函数中将数据填入数据发送寄存器,此时,这个标志位会被硬件清0.
STOPF:当主机发送停止信号结束通讯,从机接收到此型号时,该位会置1,如果此时开了IIC的中断就会进入中断函数。

从机的数据发送机制以及注意点

在主机发送地址以及读取数据的方向给从机之后,从机的TXE就会被置位。这里需要注意两个寄存器,发送数据寄存器以及移位寄存器。
发送数据寄存器:发送的数据每一个字节一开始会存在这个寄存器。
移位寄存器:发送数据寄存器将数据发给移位寄存器,移位寄存器再发给主机。
笔者在发送一帧数据(4个字节的数据)发现,主机接收到的新一帧的第一个字节的数据总是上一帧的最后要发送一个字节的数据。
比如上一帧数据为(0x00, 0x01,0x02,0x04)
新的一帧的数据为(0x05,0x06,0x07,0x08)
结果主机接收到的新的一帧数据为(0x04,0x05,0x06,0x07)
一开始还以为MCU出错,后来跟某宝的店主讨论后恍然开朗,问题出在了如下:
一开始在主机发送地址以及读取数据的方向给从机之后,此时,如果发送寄存器原来就存在上一帧的最后一个字节的数据,则这一字节的数据会直接从发送寄存器发给移位寄存器,移位寄存器再把它发给主机,从而主机接收到的新一帧的第一个字节的数据总是上一帧的最后要发送一个字节的数据放。

为什么发送寄存器会存在上一帧最后一个字节的数据呢?
还是某宝的店主告诉我的:因为MCU的IIC处理机制就是当从机IIC发送一个字节的数据给主机时,主机要应答,从机才会把存在发送寄存器的新的一个字节的数据发送给移位寄存器。
并且,一开始在主机发送地址以及读取数据的方向给从机之后,从机的发送寄存器如果存在一个字节的数据,则这一字节的数据会直接从发送寄存器发给移位寄存器,移位寄存器再把它发给主机,这第一个字节不需要主机先应答从机再发送出去。

比如这个例子:
在主机发送地址以及读取数据的方向给从机之后。如果发送寄存器原来就存在未知的一个字节的数据a,这个数据会被发送给移位寄存器(这一步是硬件自动进行的),发送寄存器为空了,此时从机的TXE位才被置位为1,产生中断事件,0x00被接着放入发送寄存器,主机接收到字节a,发送(ack)应答给从机,这个0x00才会被发送给移位寄存器。
因此,主机在接收到a,0x00,0x01,0x02,后,发现四个数据够了,就发送非应答(NACK)再接着发送停止信号,而不是应答(ack),因此0x04就被放在发送寄存器没有发送出去。
在新的一帧数据开始发送时,此时这个0x04就是上面的未知字节a了。

重点总结

1.从机产生IIC发送中断是因为发送寄存器为空,只要发送寄存器的数据发送给移位寄存器就会产生TXE置位,如果此时开了IIC的中断就会进入中断函数。
2.一开始在主机发送地址以及读取数据的方向给从机之后,从机的发送寄存器如果存在一个字节的数据,则这一字节的数据会直接从发送寄存器发给移位寄存器,移位寄存器再把它发给主机。
3.主机在接收到一个字节的数据后要应答(ack),从机才会继续把发送寄存器的数据发给移位寄存器,第一个字节除外,见2.

解决办法

实际上解决的就是在匹配到正确的地址后,就把发送寄存器的数据清空,让发送寄存器的数值重新填写,某宝的店主给我看了这段话:
芯片数据手册原话
这里的意思就是,在进入地址匹配的中断后,让IIC-ISR寄存器的TXE位置1,软件直接写I2C1->ISR |= I2C_ISR_TXE,此时会让MCU进入发送中断,从而往发送寄存器写入新的一个字节数据即可。

偷偷帮店主打个广告,祝店主生意兴隆,某宝搜多多林电子:https://shop486079607.taobao.com/category-1442399460.htm?spm=a1z10.3-c-s.w401021301227193.8.20a33ebcDBMpB2&search=y&parentCatId=1442399459&parentCatName=��������΢������&catName=ST(�ⷨ�뵼�壩#bd

大家有问题可以下面留言,需要元器件或者有问题就联系某宝店主,我也是在哪里买了东西,不会用才去问,发现原来某宝也有好多高手

以下是一个示例代码,用于两个STM32单片之间的IIC通信。在该例程中,一个STM32作为从设备(Slave)发送数据给另一个STM32作为主设备(Master)进行接收。 从设备发送数据给主设备: ```c #include "stm32f4xx.h" #include "stm32f4xx_i2c.h" #include "stm32f4xx_gpio.h" #include "stm32f4xx_rcc.h" #define I2C_ADDRESS 0x08 // 主设备地址 I2C_InitTypeDef I2C_InitStruct; GPIO_InitTypeDef GPIO_InitStruct; void I2C_Configuration(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOB, &GPIO_InitStruct); GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_I2C1); GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_I2C1); I2C_DeInit(I2C1); I2C_InitStruct.I2C_ClockSpeed = 100000; I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStruct.I2C_OwnAddress1 = 0x07; // 从设备地址 I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_Init(I2C1, &I2C_InitStruct); I2C_Cmd(I2C1, ENABLE); } void I2C_SendData(uint8_t* data, uint8_t dataLength) { while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, I2C_ADDRESS, I2C_Direction_Transmitter); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); for (uint8_t i = 0; i < dataLength; i++) { I2C_SendData(I2C1, data[i]); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); } I2C_GenerateSTOP(I2C1, ENABLE); } int main(void) { uint8_t data[] = "Hello"; I2C_Configuration(); while (1) { I2C_SendData(data, sizeof(data)); delay(500); } } ``` 主设备接收从设备发送数据: ```c #include "stm32f4xx.h" #include "stm32f4xx_i2c.h" #include "stm32f4xx_gpio.h" #include "stm32f4xx_rcc.h" #include "stm32f4xx_usart.h" #define I2C_ADDRESS 0x07 // 从设备地址 I2C_InitTypeDef I2C_InitStruct; GPIO_InitTypeDef GPIO_InitStruct; USART_InitTypeDef USART_InitStruct; void I2C_Configuration(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOB, &GPIO_InitStruct); GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_I2C1); GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_I2C1); I2C_DeInit(I2C1); I2C_InitStruct.I2C_ClockSpeed = 100000; I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStruct.I2C_OwnAddress1 = 0x08; // 主设备地址 I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_Init(I2C1, &I2C_InitStruct); I2C_Cmd(I2C1, ENABLE); } void USART_Configuration(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); USART_DeInit(USART1); USART_InitStruct.USART_BaudRate = 9600; USART_InitStruct.USART_WordLength = USART_WordLength_8b; USART_InitStruct.USART_StopBits = USART_StopBits_1; USART_InitStruct.USART_Parity = USART_Parity_No; USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStruct); USART_Cmd(USART1, ENABLE); } void I2C_ReceiveData(void) { uint8_t receivedData[10]; uint8_t dataLength = 0; while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); I2C_AcknowledgeConfig(I2C1, ENABLE); I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, I2C_ADDRESS, I2C_Direction_Receiver); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); while (dataLength < sizeof(receivedData)) { if (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)) { receivedData[dataLength++] = I2C_ReceiveData(I2C1); } } I2C_AcknowledgeConfig(I2C1, DISABLE); I2C_GenerateSTOP(I2C1, ENABLE); for (uint8_t i = 0; i < dataLength; i++) { while (!USART_GetFlagStatus(USART1, USART_FLAG_TXE)); USART_SendData(USART1, receivedData[i]); } } int main(void) { I2C_Configuration(); USART_Configuration(); while (1) { I2C_ReceiveData(); delay(500); } } ``` 在从设备中,我们使用`I2C_SendData()`函数发送数据给主设备。首先,我们等待IIC总线空闲,然后生成START信号,接着发送主设备地址和写入位,然后通过循环发送数据字节。最后,我们生成STOP信号结束传输。 在主设备中,我们使用`I2C_ReceiveData()`函数接收从设备发送数据。首先,我们等待IIC总线空闲,然后生成START信号,接着发送从设备地址和读取位。然后,我们循环接收数据字节,并通过USART发送到串口进行打印。 请注意:以上示例代码是基于STM32F4系列的,使用STM32标准外设库(Standard Peripherals Library)。如果你使用其他型号的STM32单片机或者使用了不同的开发库,可能需要进行相应的调整。 希望这个例程对你有所帮助!如果还有其他问题,请随时提问。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值