STM8的I2C在写地址的时候进入到assert_failed

本文详细解析了STM8 I2C库函数在使用过程中遇到的一个常见问题:在调用I2C_Send7bitAddress函数时,因参数错误导致程序卡死的情况。文章不仅介绍了问题产生的原因,还提供了具体的解决办法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

近期使用stm8的库函数写功能,使用I2C时候遇到在调用 void I2C_Send7bitAddress(uint8_t Address, I2C_Direction_TypeDef Direction)的时候,会卡死的问题


通过调试发现在调用写地址函数后会进入到main.c的这个函数中

void assert_failed(u8* file, u32 line)
{ 
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
	//	 	printf("Wrong parameters value: file %s on line %d\r\n", file, line);
  /* Infinite loop */
  while (1)
  {// return;
  }
}




后来发现写地址函数会先检测输入参数是否正确如果不正确就会进入错误循环


贴上这个官方I2C库函数的代码


void I2C_Send7bitAddress(uint8_t Address, I2C_Direction_TypeDef Direction)
{
  /* Check function parameters */
  assert_param(IS_I2C_ADDRESS_OK(Address));
  assert_param(IS_I2C_DIRECTION_OK(Direction));

  /* Clear bit0 (direction) just in case */
  Address &= (uint8_t)0xFE;

  /* Send the Address + Direction */
  I2C->DR = (uint8_t)(Address | (uint8_t)Direction);
}

这里

/* Check function parameters */
  assert_param(IS_I2C_ADDRESS_OK(Address));
  assert_param(IS_I2C_DIRECTION_OK(Direction));
注释的两句话就是检测参数是否正确,有一个比较坑的地方就在于,如果你的地址是八位的,然后最后一位读写位不为0,那么就会出错


STM8这个比较特殊的一点在于,正常7位地址+1位RW位,这个RW位你必须自己留出来,比如某个设备地址是111 1100,我在其他环境下会声明为0x7C


但是在这边,你要用它的库就需要声明为1111 1000,0xF8才可以。


最后一位如果值为1,必出错跳转faild死循环


/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "spi.h" #include "usart.h" #include "gpio.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ //#include "i2c_sw.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include "delay.h" #include "spi_480_272_lcd.h" #include "display.h" #include "soft_i2c.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ //typedef uint32_t u32; //typedef uint16_t u16; //typedef uint8_t u8; //typedef const uint8_t uc8; // //#ifndef __cplusplus //typedef enum {FALSE = 0, TRUE = !FALSE} bool; //#endif // 错误代码扩展(与soft_i2c.h对应) #define I2C_SUCCESS 0 #define I2C_ERROR_ACK 1 #define I2C_ERROR_TIMEOUT 2 // 新增:超时错误 // I2C设备参数(根据实际硬件修改) #define SLAVE_DEVICE_ADDR 0x48 // 7位从机地址(不含读位) #define TEST_REG_ADDR 0x00 // 测试用寄存器地址 #define I2C_RETRY_COUNT 3 // 通信失败重试次数 /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ // 函数声明 void I2C_CommunicationTest(void); void PrintData(uint8_t* data, uint8_t len); uint8_t I2C_WriteWithRetry(uint8_t devAddr, uint8_t* data, uint8_t len); uint8_t I2C_ReadWithRetry(uint8_t devAddr, uint8_t* data, uint8_t len); uint8_t I2C_WriteRegWithRetry(uint8_t devAddr, uint8_t regAddr, uint8_t* data, uint8_t len); uint8_t I2C_ReadRegWithRetry(uint8_t devAddr, uint8_t regAddr, uint8_t* data, uint8_t len); /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ uint8_t g_i2cStatus = I2C_SUCCESS; // 全局I2C状态,用于调试 /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_SPI1_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ // 初始化硬件资源 I2C_Init(); // 初始化软件I2C printf("系统初始化完成,I2C引脚配置为开漏输出+外部上拉\r\n"); printf("从机地址: 0x%02X,测试寄存器: 0x%02X\r\n", SLAVE_DEVICE_ADDR, TEST_REG_ADDR); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { I2C_CommunicationTest(); // 执行I2C通信测试 Delay_Ms(1000); // 1秒测试间隔 /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } /* USER CODE BEGIN 4 */ // printf重定向到USART1 int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF); return ch; } // 带重试机制的I2C操作(提高可靠性) uint8_t I2C_WriteWithRetry(uint8_t devAddr, uint8_t* data, uint8_t len) { uint8_t retry = 0; while (retry < I2C_RETRY_COUNT) { g_i2cStatus = I2C_Write(devAddr, data, len); if (g_i2cStatus == I2C_SUCCESS) return I2C_SUCCESS; retry++; Delay_Ms(50); // 重试前延时 I2C_Stop(); // 确保总线释放 } return g_i2cStatus; } // 带重试机制的I2C读操作 uint8_t I2C_ReadWithRetry(uint8_t devAddr, uint8_t* data, uint8_t len) { uint8_t retry = 0; while (retry < I2C_RETRY_COUNT) { g_i2cStatus = I2C_Read(devAddr, data, len); if (g_i2cStatus == I2C_SUCCESS) return I2C_SUCCESS; retry++; Delay_Ms(50); I2C_Stop(); } return g_i2cStatus; } // 带重试机制的寄存器操作 uint8_t I2C_WriteRegWithRetry(uint8_t devAddr, uint8_t regAddr, uint8_t* data, uint8_t len) { uint8_t retry = 0; while (retry < I2C_RETRY_COUNT) { g_i2cStatus = I2C_WriteReg(devAddr, regAddr, data, len); if (g_i2cStatus == I2C_SUCCESS) return I2C_SUCCESS; retry++; Delay_Ms(50); I2C_Stop(); } return g_i2cStatus; } // 带重试机制的寄存器读操作 uint8_t I2C_ReadRegWithRetry(uint8_t devAddr, uint8_t regAddr, uint8_t* data, uint8_t len) { uint8_t retry = 0; while (retry < I2C_RETRY_COUNT) { g_i2cStatus = I2C_ReadReg(devAddr, regAddr, data, len); if (g_i2cStatus == I2C_SUCCESS) return I2C_SUCCESS; retry++; Delay_Ms(50); I2C_Stop(); } return g_i2cStatus; } // I2C通信测试主函数 void I2C_CommunicationTest(void) { uint8_t writeData[5] = {0x11, 0x22, 0x33, 0x44, 0x55}; uint8_t readData[5] = {0}; uint8_t status; printf("\r\n===== 开始I2C通信测试 ====="); // 测试1: 直接向从机入数据(无寄存器地址) status = I2C_WriteWithRetry(SLAVE_DEVICE_ADDR, writeData, 5); if (status == I2C_SUCCESS) { printf("\r\n测试1: 直接入成功: "); PrintData(writeData, 5); } else { printf("\r\n测试1: 直接入失败,错误代码: %d(重试%d次后)", status, I2C_RETRY_COUNT); } Delay_Ms(100); // 等待从机处理 // 测试2: 直接从从机读取数据(无寄存器地址) status = I2C_ReadWithRetry(SLAVE_DEVICE_ADDR, readData, 5); if (status == I2C_SUCCESS) { printf("测试2: 直接读取成功: "); PrintData(readData, 5); // 验证读取数据与入数据是否一致(仅适用于支持回读的设备) if (memcmp(writeData, readData, 5) == 0) printf(" 数据校验一致"); else printf(" 数据校验不一致"); } else { printf("\r\n测试2: 直接读取失败,错误代码: %d(重试%d次后)", status, I2C_RETRY_COUNT); } Delay_Ms(100); memset(readData, 0, 5); // 清空接收缓存 // 测试3: 向指定寄存器入数据 status = I2C_WriteRegWithRetry(SLAVE_DEVICE_ADDR, TEST_REG_ADDR, writeData, 5); if (status == I2C_SUCCESS) { printf("\r\n测试3: 寄存器0x%02X入成功: ", TEST_REG_ADDR); PrintData(writeData, 5); } else { printf("\r\n测试3: 寄存器0x%02X入失败,错误代码: %d(重试%d次后)", TEST_REG_ADDR, status, I2C_RETRY_COUNT); } Delay_Ms(100); // 测试4: 从指定寄存器读取数据 status = I2C_ReadRegWithRetry(SLAVE_DEVICE_ADDR, TEST_REG_ADDR, readData, 5); if (status == I2C_SUCCESS) { printf("测试4: 寄存器0x%02X读取成功: ", TEST_REG_ADDR); PrintData(readData, 5); // 验证寄存器读一致性 if (memcmp(writeData, readData, 5) == 0) printf(" 数据校验一致"); else printf(" 数据校验不一致"); } else { printf("\r\n测试4: 寄存器0x%02X读取失败,错误代码: %d(重试%d次后)", TEST_REG_ADDR, status, I2C_RETRY_COUNT); } printf("\r\n===== 测试结束 ====="); } // 打印数据(十六进制格式) void PrintData(uint8_t* data, uint8_t len) { for (uint8_t i = 0; i < len; i++) { printf("0x%02X ", data[i]); if ((i + 1) % 5 == 0) // 每5个数据换行 printf("\r\n "); } } /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ #include "soft_i2c.h" #include "delay.h" // 引入自定义延时函数头文件 // 设置SDA为输出模式 void I2C_SetSDAOutputMode(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = I2C_SDA_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 开漏输出 GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(I2C_SDA_PORT, &GPIO_InitStruct); } // 设置SDA为输入模式 void I2C_SetSDAInputMode(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = I2C_SDA_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 输入模式 GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(I2C_SDA_PORT, &GPIO_InitStruct); } // 初始化I2C引脚 - 使用HAL库函数 void I2C_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; // 使能GPIO时钟 __HAL_RCC_GPIOC_CLK_ENABLE(); // 配置SCL引脚 GPIO_InitStruct.Pin = I2C_SCL_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 开漏输出 GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(I2C_SCL_PORT, &GPIO_InitStruct); // 配置SDA引脚为输出模式 I2C_SetSDAOutputMode(); // 初始化引脚为高电平 I2C_SCL_HIGH; I2C_SDA_HIGH; Delay_Ms(10); // 使用毫秒延时 } // 产生I2C起始信号 // 起始条件:SCL高电平时,SDA从高到低跳变 void I2C_Start(void) { I2C_SetSDAOutputMode(); I2C_SDA_HIGH; I2C_SCL_HIGH; I2C_delay(); // 使用专用I2C延时函数 I2C_SDA_LOW; // 拉低SDA,产生起始信号 I2C_delay(); I2C_SCL_LOW; // 拉低SCL,准备发送数据 I2C_delay(); } // 产生I2C停止信号 // 停止条件:SCL高电平时,SDA从低到高跳变 void I2C_Stop(void) { I2C_SetSDAOutputMode(); I2C_SDA_LOW; I2C_delay(); I2C_SCL_HIGH; I2C_delay(); I2C_SDA_HIGH; // 拉高SDA,产生停止信号 I2C_delay(); } // 发送应答信号 // ack=0: 应答(ACK), ack=1: 非应答(NACK) void I2C_SendAck(uint8_t ack) { I2C_SetSDAOutputMode(); if (ack) { I2C_SDA_HIGH; // 非应答(NACK) } else { I2C_SDA_LOW; // 应答(ACK) } I2C_delay(); I2C_SCL_HIGH; // 时钟高电平,从机读取应答 I2C_delay(); I2C_SCL_LOW; // 拉低时钟 I2C_delay(); I2C_SDA_HIGH; // 释放SDA } // 接收应答信号 // 返回0: 应答(ACK), 返回1: 非应答(NACK) uint8_t I2C_ReceiveAck(void) { uint8_t ack; I2C_SetSDAInputMode(); I2C_delay(); I2C_SCL_HIGH; // 时钟高电平,读取应答 I2C_delay(); ack = I2C_SDA_READ; // 读取应答信号 I2C_SCL_LOW; // 拉低时钟 I2C_delay(); I2C_SetSDAOutputMode(); return ack; } // 发送一个字节数据 void I2C_SendByte(uint8_t data) { uint8_t i; I2C_SetSDAOutputMode(); for (i = 0; i < 8; i++) { // 发送高位 if (data & 0x80) { I2C_SDA_HIGH; } else { I2C_SDA_LOW; } data <<= 1; I2C_delay(); I2C_SCL_HIGH; // 时钟高电平,从机读取数据 I2C_delay(); I2C_SCL_LOW; // 拉低时钟,准备下一位数据 I2C_delay(); } } // 接收一个字节数据 uint8_t I2C_ReceiveByte(void) { uint8_t i, data = 0; I2C_SetSDAInputMode(); for (i = 0; i < 8; i++) { data <<= 1; I2C_SCL_HIGH; // 时钟高电平,读取数据 I2C_delay(); if (I2C_SDA_READ) { data |= 0x01; } I2C_SCL_LOW; // 拉低时钟,准备下一位 I2C_delay(); } I2C_SetSDAOutputMode(); return data; } // 向I2C设备发送数据 uint8_t I2C_Write(uint8_t devAddr, uint8_t* data, uint8_t len) { uint8_t i; I2C_Start(); // 发送起始信号 I2C_SendByte((devAddr << 1) & 0xFE); // 发送设备地址+标志 if (I2C_ReceiveAck() != 0) { // 等待应答 I2C_Stop(); return I2C_ERROR_ACK; // 无应答,失败 } for (i = 0; i < len; i++) { I2C_SendByte(data[i]); // 发送数据 if (I2C_ReceiveAck() != 0) { // 等待应答 I2C_Stop(); return I2C_ERROR_ACK; // 无应答,失败 } } I2C_Stop(); // 发送停止信号 return I2C_SUCCESS; // 成功 } // 向I2C设备指定寄存器发送数据 uint8_t I2C_WriteReg(uint8_t devAddr, uint8_t regAddr, uint8_t* data, uint8_t len) { uint8_t i; I2C_Start(); // 发送起始信号 I2C_SendByte((devAddr << 1) & 0xFE); // 发送设备地址+标志 if (I2C_ReceiveAck() != 0) { // 等待应答 I2C_Stop(); return I2C_ERROR_ACK; // 失败 } I2C_SendByte(regAddr); // 发送寄存器地址 if (I2C_ReceiveAck() != 0) { // 等待应答 I2C_Stop(); return I2C_ERROR_ACK; // 失败 } for (i = 0; i < len; i++) { I2C_SendByte(data[i]); // 发送数据 if (I2C_ReceiveAck() != 0) { // 等待应答 I2C_Stop(); return I2C_ERROR_ACK; // 失败 } } I2C_Stop(); // 发送停止信号 return I2C_SUCCESS; // 成功 } // 从I2C设备读取数据 uint8_t I2C_Read(uint8_t devAddr, uint8_t* data, uint8_t len) { uint8_t i; I2C_Start(); // 发送起始信号 I2C_SendByte((devAddr << 1) | 0x01); // 发送设备地址+读标志 if (I2C_ReceiveAck() != 0) { // 等待应答 I2C_Stop(); return I2C_ERROR_ACK; // 失败 } for (i = 0; i < len; i++) { data[i] = I2C_ReceiveByte(); // 接收数据 // 最后一个字节发送非应答,其他发送应答 if (i == len - 1) { I2C_SendAck(1); // 非应答 } else { I2C_SendAck(0); // 应答 } } I2C_Stop(); // 发送停止信号 return I2C_SUCCESS; // 成功 } // 从I2C设备指定寄存器读取数据 uint8_t I2C_ReadReg(uint8_t devAddr, uint8_t regAddr, uint8_t* data, uint8_t len) { // 先发送寄存器地址 I2C_Start(); I2C_SendByte((devAddr << 1) & 0xFE); // 操作 if (I2C_ReceiveAck() != 0) { I2C_Stop(); return I2C_ERROR_ACK; // 失败 } I2C_SendByte(regAddr); // 发送寄存器地址 if (I2C_ReceiveAck() != 0) { I2C_Stop(); return I2C_ERROR_ACK; // 失败 } // 重新发送起始信号,开始读取数据 return I2C_Read(devAddr, data, len); } 这是STM32F103VCT6的主函数以及GPIO模拟I2C的代码,与以下从机代码,I2C通讯不成功,请改善。/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "adc.h" #include "dma.h" #include "tim.h" #include "usart.h" #include "gpio.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "delay.h" #include "soft_i2c.h" #include "stm32g0xx_hal.h" #include <string.h> #include <stdio.h> /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ // 从机地址定义(需与主机中设置的SLAVE_DEVICE_ADDR一致) #define I2C_SLAVE_ADDR 0x48 // 缓冲区大小(需在soft_i2c.h中定义) #define I2C_RX_BUFFER_SIZE 32 #define I2C_TX_BUFFER_SIZE 32 /* 定义设备地址和串口配置 */ #define DEVICE_COUNT 8 // 设备总数 #define UART_PER_DEVICE 4 // 每个设备的串口数 #define I2C_SLAVE_ADDRESS 0x48 // I2C从机地址 /* 传感器数据结构 */ typedef struct { uint16_t adValue; // AD值 uint8_t deviceId; // 设备ID uint8_t uartId; // 串口ID uint8_t crcValid; // CRC校验结果 uint8_t data5; // 第五位数据 uint8_t data6; // 第六位数据 } SensorData; // I2C通信状态枚举 typedef enum { I2C_CMD_NONE, I2C_CMD_READ_SENSOR, // 读取传感器数据(主机指令) I2C_CMD_WRITE_REG, // 入寄存器(主机指令) I2C_CMD_READ_REG // 读取寄存器(主机指令) } I2C_CmdTypeDef; /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ // 寄存器定义(与主机TEST_REG_ADDR对应) #define TEST_REG_ADDR 0x00 // 测试用寄存器地址 #define REG_BUFFER_SIZE 16 // 寄存器缓冲区大小 // 函数声明 void ProcessI2CCommand(void); void UpdateSensorData(void); uint16_t CRC16_Calculate(uint8_t *data, uint16_t length); // 函数声明 void I2C_Slave_HandleData(void); void I2C_Slave_PrepareResponse(void); /* 全局变量 */ extern UART_HandleTypeDef huart1; extern UART_HandleTypeDef huart2; extern UART_HandleTypeDef huart3; extern UART_HandleTypeDef huart4; UART_HandleTypeDef *uartHandles[UART_PER_DEVICE] = {&huart1, &huart2, &huart3, &huart4}; SensorData sensorData[UART_PER_DEVICE]; uint8_t modbusTxBuf[8]; uint8_t modbusRxBuf[10]; uint16_t crcTemp; uint8_t deviceId = 0; // 当前设备ID (0-7) /* CRC校验函数 */ uint16_t CRC16_Calculate(uint8_t *data, uint16_t length) { uint16_t crc = 0; uint8_t crc1 = 0, crc2 = 0; uint16_t i; for (i = 0; i < length; i++) { if ((i % 2) == 0) crc1 += data[i]; crc2 += data[i]; } crc = crc2; crc *= crc1; crc *= 7639; return crc; } uint8_t CRC16_Verify(uint8_t *data, uint16_t length) { uint16_t crc_calculated, crc_received; crc_calculated = CRC16_Calculate(data, length - 2); crc_received = (data[length - 2] << 8) | data[length - 1]; return (crc_calculated == crc_received); } /* 传感器通信函数 */ uint8_t ReadSensorData(uint8_t uartId, uint8_t reg) { uint8_t i; uint32_t timeout; /* 清空接收缓冲区 */ for (i = 0; i < 10; i++) modbusRxBuf[i] = 0; /* 构建Modbus请求 */ modbusTxBuf[0] = 1; modbusTxBuf[1] = 0xAA; modbusTxBuf[2] = 0x03; // 读保持寄存器功能码 modbusTxBuf[3] = reg; modbusTxBuf[4] = 0; modbusTxBuf[5] = 1; /* 计算CRC并添加到发送缓冲区 */ crcTemp = CRC16_Calculate(modbusTxBuf, 6); modbusTxBuf[6] = crcTemp >> 8; modbusTxBuf[7] = crcTemp & 0xFF; /* 发送请求 */ HAL_UART_Transmit(uartHandles[uartId], modbusTxBuf, 8, 1000); /* 等待接收响应 */ timeout = 100000; while (timeout-- && modbusRxBuf[0] != 0x01); // 等待响应头 if (timeout == 0) return 0; // 接收超时 /* 接收完整响应 */ timeout = 100000; while (timeout-- && modbusRxBuf[1] != 0xBB); // 等待响应标志 if (timeout == 0) return 0; // 接收超时 /* 验证CRC */ sensorData[uartId].crcValid = CRC16_Verify(modbusRxBuf, 8); return 0; // CRC校验失败 } //typedef uint32_t u32; //typedef uint16_t u16; //typedef uint8_t u8; //typedef const uint8_t uc8; // //#ifndef __cplusplus //typedef enum {FALSE = 0, TRUE = !FALSE} bool; //#endif /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ uint8_t regBuffer[REG_BUFFER_SIZE] = {0}; // 寄存器缓冲区(模拟从机寄存器) I2C_CmdTypeDef i2cCmd = I2C_CMD_NONE; // 当前I2C命令 uint8_t currentRegAddr = 0; // 当前操作的寄存器地址 /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ void I2C_Slave_Process(void); /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_DMA_Init(); MX_ADC1_Init(); MX_USART1_UART_Init(); MX_USART2_UART_Init(); MX_USART3_UART_Init(); MX_USART4_UART_Init(); MX_TIM6_Init(); /* USER CODE BEGIN 2 */ uint8_t i; // 初始化I2C从机(开漏输出+上拉,与主机硬件匹配) I2C_Slave_Init(); // 初始化寄存器(默认值) regBuffer[TEST_REG_ADDR] = 0x00; printf("I2C从机初始化完成,地址:0x%02X\r\n", I2C_SLAVE_ADDR); printf("等待主机通信...\r\n"); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* 轮询所有串口读取传感器数据 */ for (i = 0; i < UART_PER_DEVICE; i++) { ReadSensorData(i, 0x0B); HAL_Delay(10); // 简单延时,避免总线竞争 } // 2. 处理I2C通信(必须高频调用,确保不遗漏主机指令) I2C_Slave_Process(); // 3. 检查是否收到主机数据,若有则处理并生成应答 if (I2C_Slave_GetRxDataLength() > 0) { ProcessI2CCommand(); // 解析主机命令 I2C_Slave_ClearRxBuffer(); // 清空接收缓冲区 } // 4. 短暂延时,降低CPU占用 Delay_Ms(1); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Configure the main internal regulator output voltage */ HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1); /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV1; RCC_OscInitStruct.PLL.PLLN = 8; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } /* USER CODE BEGIN 4 */ // 处理主机发送的I2C命令 void ProcessI2CCommand(void) { uint8_t rxData[I2C_RX_BUFFER_SIZE]; uint8_t rxLen = I2C_Slave_GetRxDataLength(); uint8_t txData[I2C_TX_BUFFER_SIZE]; uint8_t txLen = 0; // 读取主机发送的数据 I2C_Slave_ReadRxBuffer(rxData, rxLen); printf("收到主机数据(长度:%d):", rxLen); for (uint8_t i = 0; i < rxLen; i++) { printf("0x%02X ", rxData[i]); } printf("\r\n"); // 解析主机命令(根据主机操作类型适配) // 情况1:主机执行"直接"(无寄存器地址,如测试1) if (rxLen >= 1) { // 保存数据到寄存器(模拟从机存储) memcpy(regBuffer, rxData, (rxLen < REG_BUFFER_SIZE) ? rxLen : REG_BUFFER_SIZE); i2cCmd = I2C_CMD_WRITE_REG; } // 情况2:主机执行"寄存器"(含寄存器地址,如测试3) // 主机格式:[REG_ADDR, DATA1, DATA2, ...] if (rxLen >= 2) { currentRegAddr = rxData[0]; // 寄存器地址 if (currentRegAddr < REG_BUFFER_SIZE) { // 入寄存器数据 uint8_t dataLen = rxLen - 1; memcpy(&regBuffer[currentRegAddr], &rxData[1], (dataLen <= REG_BUFFER_SIZE - currentRegAddr) ? dataLen : (REG_BUFFER_SIZE - currentRegAddr)); i2cCmd = I2C_CMD_WRITE_REG; } } // 情况3:主机执行"直接读"或"寄存器读"(需返回对应数据) // 生成应答数据(与主机读取逻辑匹配) if (i2cCmd == I2C_CMD_WRITE_REG || i2cCmd == I2C_CMD_NONE) { // 若主机后读,则返回入的寄存器数据(确保主机memcmp校验通过) if (currentRegAddr < REG_BUFFER_SIZE) { // 读取寄存器数据作为应答(长度与主机入一致) txLen = (rxLen < I2C_TX_BUFFER_SIZE) ? rxLen : I2C_TX_BUFFER_SIZE; memcpy(txData, &regBuffer[currentRegAddr], txLen); } else { // 寄存器地址无效,返回错误码0xFF txData[txLen++] = 0xFF; } } else if (i2cCmd == I2C_CMD_READ_SENSOR) { // 若主机读取传感器数据,返回传感器AD值(示例) txData[txLen++] = (sensorData[0].adValue >> 8) & 0xFF; // 高8位 txData[txLen++] = sensorData[0].adValue & 0xFF; // 低8位 txData[txLen++] = sensorData[0].crcValid; // CRC结果 } // 将应答数据入发送缓冲区,供主机读取 I2C_Slave_WriteTxBuffer(txData, txLen); printf("已准备应答数据(长度:%d):", txLen); for (uint8_t i = 0; i < txLen; i++) { printf("0x%02X ", txData[i]); } printf("\r\n"); // 重置命令状态 i2cCmd = I2C_CMD_NONE; } /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ #include "soft_i2c.h" #include "delay.h" // 引入自定义延时函数头文件 #include <string.h> // 全局变量 static I2C_Slave_StateTypeDef i2c_state = I2C_SLAVE_IDLE; static uint8_t rx_buffer[I2C_RX_BUFFER_SIZE]; static uint8_t rx_index = 0; static uint8_t tx_buffer[I2C_TX_BUFFER_SIZE]; static uint8_t tx_index = 0; static uint8_t tx_length = 0; static uint8_t received_addr = 0; static uint8_t is_read_mode = 0; // 配置SDA为输出模式 static void I2C_SetSDAOutputMode(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = I2C_SLAVE_SDA_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(I2C_SLAVE_SDA_PORT, &GPIO_InitStruct); } // 配置SDA为输入模式 static void I2C_SetSDAInputMode(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = I2C_SLAVE_SDA_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(I2C_SLAVE_SDA_PORT, &GPIO_InitStruct); } // 产生应答信号 static void I2C_SendAck(void) { I2C_SetSDAOutputMode(); HAL_GPIO_WritePin(I2C_SLAVE_SDA_PORT, I2C_SLAVE_SDA_PIN, GPIO_PIN_RESET); // 拉低SDA表示应答 I2C_delay(); // 使用I2C专用延时 } // 产生非应答信号 static void I2C_SendNack(void) { I2C_SetSDAOutputMode(); HAL_GPIO_WritePin(I2C_SLAVE_SDA_PORT, I2C_SLAVE_SDA_PIN, GPIO_PIN_SET); // 拉高SDA表示非应答 I2C_delay(); // 使用I2C专用延时 } // 释放SDA线 static void I2C_ReleaseSDA(void) { I2C_SetSDAInputMode(); } // 读取SDA线状态 static uint8_t I2C_ReadSDA(void) { return HAL_GPIO_ReadPin(I2C_SLAVE_SDA_PORT, I2C_SLAVE_SDA_PIN); } // 初始化I2C从机 void I2C_Slave_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; // 使能GPIO时钟 __HAL_RCC_GPIOB_CLK_ENABLE(); // 配置SCL引脚为输入,上拉 GPIO_InitStruct.Pin = I2C_SLAVE_SCL_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(I2C_SLAVE_SCL_PORT, &GPIO_InitStruct); // 配置SDA引脚为输入,上拉 GPIO_InitStruct.Pin = I2C_SLAVE_SDA_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(I2C_SLAVE_SDA_PORT, &GPIO_InitStruct); // 初始化缓冲区 I2C_Slave_ClearRxBuffer(); I2C_Slave_ClearTxBuffer(); // 初始化状态 i2c_state = I2C_SLAVE_IDLE; } // 等待SCL线变为高电平 static uint8_t I2C_WaitForSCLHigh(uint32_t timeout_ms) { uint32_t start_time = HAL_GetTick(); while (HAL_GPIO_ReadPin(I2C_SLAVE_SCL_PORT, I2C_SLAVE_SCL_PIN) == GPIO_PIN_RESET) { if ((HAL_GetTick() - start_time) >= timeout_ms) { return 0; // 超时 } Delay1us(1); // 1us短暂延时 } return 1; // 成功 } // 等待SCL线变为低电平 static uint8_t I2C_WaitForSCLLow(uint32_t timeout_ms) { uint32_t start_time = HAL_GetTick(); while (HAL_GPIO_ReadPin(I2C_SLAVE_SCL_PORT, I2C_SLAVE_SCL_PIN) == GPIO_PIN_SET) { if ((HAL_GetTick() - start_time) >= timeout_ms) { return 0; // 超时 } Delay1us(1); // 1us短暂延时 } return 1; // 成功 } // 检测起始信号 static uint8_t I2C_DetectStart(void) { // 起始条件:SCL高电平时,SDA从高到低跳变 if (HAL_GPIO_ReadPin(I2C_SLAVE_SCL_PORT, I2C_SLAVE_SCL_PIN) == GPIO_PIN_SET && I2C_ReadSDA() == GPIO_PIN_RESET) { Delay1us(2); // 2us延时 // 再次确认 if (HAL_GPIO_ReadPin(I2C_SLAVE_SCL_PORT, I2C_SLAVE_SCL_PIN) == GPIO_PIN_SET && I2C_ReadSDA() == GPIO_PIN_RESET) { return 1; // 检测到起始信号 } } return 0; } // 检测停止信号 static uint8_t I2C_DetectStop(void) { // 停止条件:SCL高电平时,SDA从低到高跳变 if (HAL_GPIO_ReadPin(I2C_SLAVE_SCL_PORT, I2C_SLAVE_SCL_PIN) == GPIO_PIN_SET && I2C_ReadSDA() == GPIO_PIN_SET) { Delay1us(2); // 2us延时 // 再次确认 if (HAL_GPIO_ReadPin(I2C_SLAVE_SCL_PORT, I2C_SLAVE_SCL_PIN) == GPIO_PIN_SET && I2C_ReadSDA() == GPIO_PIN_SET) { return 1; // 检测到停止信号 } } return 0; } // 接收一个字节 static uint8_t I2C_ReceiveByte(void) { uint8_t data = 0; uint8_t i; I2C_SetSDAInputMode(); for (i = 0; i < 8; i++) { // 等待SCL变为高电平 if (!I2C_WaitForSCLHigh(1)) { return 0; // 超时 } // 读取数据位(高位在前) data <<= 1; if (I2C_ReadSDA()) { data |= 0x01; } Delay1us(2); // 2us延时 // 等待SCL变为低电平 if (!I2C_WaitForSCLLow(1)) { return 0; // 超时 } } return data; } // 发送一个字节 static void I2C_SendByte(uint8_t data) { uint8_t i; I2C_SetSDAOutputMode(); for (i = 0; i < 8; i++) { // 设置数据位(高位在前) if (data & 0x80) { HAL_GPIO_WritePin(I2C_SLAVE_SDA_PORT, I2C_SLAVE_SDA_PIN, GPIO_PIN_SET); } else { HAL_GPIO_WritePin(I2C_SLAVE_SDA_PORT, I2C_SLAVE_SDA_PIN, GPIO_PIN_RESET); } data <<= 1; Delay1us(2); // 2us延时 // 等待SCL变为高电平 I2C_WaitForSCLHigh(1); Delay1us(2); // 2us延时 // 等待SCL变为低电平 I2C_WaitForSCLLow(1); } I2C_ReleaseSDA(); // 释放SDA线,准备接收应答 } // I2C从机处理函数,需要在主循环中调用 void I2C_Slave_Process(void) { uint8_t ack; // 检测起始信号 if (I2C_DetectStart()) { i2c_state = I2C_SLAVE_ADDR_MATCHED; rx_index = 0; tx_index = 0; // 接收地址字节 received_addr = I2C_ReceiveByte(); // 检查地址是否匹配 if ((received_addr & 0xFE) == (I2C_SLAVE_ADDR << 1)) { // 地址匹配,确定读模式 is_read_mode = (received_addr & 0x01); // 发送应答 I2C_SendAck(); // 等待SCL变为低电平 I2C_WaitForSCLLow(1); if (is_read_mode) { i2c_state = I2C_SLAVE_SENDING; } else { i2c_state = I2C_SLAVE_RECEIVING; } } else { // 地址不匹配,不发送应答 I2C_SendNack(); i2c_state = I2C_SLAVE_IDLE; } return; } // 检测停止信号 if (I2C_DetectStop()) { i2c_state = I2C_SLAVE_IDLE; return; } // 根据当前状态处理 switch (i2c_state) { case I2C_SLAVE_RECEIVING: // 接收数据 if (rx_index < I2C_RX_BUFFER_SIZE) { rx_buffer[rx_index++] = I2C_ReceiveByte(); I2C_SendAck(); // 发送应答 I2C_WaitForSCLLow(1); } else { // 缓冲区已满,发送非应答 I2C_ReceiveByte(); // 仍然读取数据但不存储 I2C_SendNack(); I2C_WaitForSCLLow(1); } break; case I2C_SLAVE_SENDING: // 发送数据 if (tx_index < tx_length) { I2C_SendByte(tx_buffer[tx_index++]); // 等待主机应答 I2C_WaitForSCLHigh(1); ack = I2C_ReadSDA(); I2C_WaitForSCLLow(1); if (ack) { // 收到非应答,停止发送 i2c_state = I2C_SLAVE_IDLE; } } else { // 没有更多数据发送,发送0xFF I2C_SendByte(0xFF); // 等待主机应答 I2C_WaitForSCLHigh(1); I2C_WaitForSCLLow(1); i2c_state = I2C_SLAVE_IDLE; } break; default: break; } } // 获取接收缓冲区的数据长度 uint8_t I2C_Slave_GetRxDataLength(void) { return rx_index; } // 读取接收缓冲区数据 uint8_t I2C_Slave_ReadRxBuffer(uint8_t *data, uint8_t len) { uint8_t i; if (data == NULL || len == 0) { return 0; } uint8_t read_len = (len < rx_index) ? len : rx_index; for (i = 0; i < read_len; i++) { data[i] = rx_buffer[i]; } return read_len; } // 清空接收缓冲区 void I2C_Slave_ClearRxBuffer(void) { rx_index = 0; memset(rx_buffer, 0, I2C_RX_BUFFER_SIZE); } // 入发送缓冲区 uint8_t I2C_Slave_WriteTxBuffer(uint8_t *data, uint8_t len) { if (data == NULL || len == 0 || len > I2C_TX_BUFFER_SIZE) { return 0; } memcpy(tx_buffer, data, len); tx_length = len; tx_index = 0; return len; } // 清空发送缓冲区 void I2C_Slave_ClearTxBuffer(void) { tx_length = 0; tx_index = 0; memset(tx_buffer, 0, I2C_TX_BUFFER_SIZE); }
最新发布
08-05
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值