*** Using Compiler 'V5.06 update 5 (build 528)', folder: 'D:\keil c51\ARM\ARMCC\Bin' Rebuild target 'Target 1' assembling startup_stm32f10x_md.s... compiling core_cm3.c... compiling delay.c... compiling stm32f10x_crc.c... compiling system_stm32f10x.c... compiling stm32f10x_dbgmcu.c... compiling stm32f10x_bkp.c... compiling misc.c... compiling stm32f10x_cec.c... compiling stm32f10x_adc.c... compiling stm32f10x_dac.c... compiling stm32f10x_can.c... compiling stm32f10x_dma.c... compiling stm32f10x_exti.c... compiling stm32f10x_flash.c... compiling stm32f10x_fsmc.c... compiling stm32f10x_gpio.c... compiling stm32f10x_i2c.c... compiling stm32f10x_rtc.c... compiling stm32f10x_iwdg.c... compiling stm32f10x_pwr.c... compiling stm32f10x_sdio.c... compiling stm32f10x_rcc.c... compiling stm32f10x_spi.c... compiling stm32f10x_usart.c... compiling stm32f10x_tim.c... compiling stm32f10x_wwdg.c... compiling main.c... compiling stm32f10x_it.c... linking... .\Objects\模板工程.axf: Error: L6200E: Symbol delay_us multiply defined (by main.o and delay.o). Not enough information to list image symbols. Not enough information to list load addresses in the image map. Finished: 2 information, 0 warning and 1 error messages. ".\Objects\模板工程.axf" - 1 Error(s), 0 Warning(s). Target not created. Build Time Elapsed: 00:00:02
时间: 2025-07-22 11:37:21 浏览: 25
<think>根据错误信息,`delay_us` 函数被重复定义了。这通常是因为在多个源文件中定义了同名的函数。从错误信息中可以看出,`main.o` 和 `delay.o` 都包含了 `delay_us` 的定义。
解决方案:
1. 如果项目中有独立的 `delay.c` 文件(其中已经定义了 `delay_us` 函数),那么我们在 `main.c` 中就不应该再次定义这个函数。
2. 我们可以在 `main.c` 中移除 `delay_us` 函数的定义,改为使用 `delay.c` 中提供的函数。同时,在 `main.c` 中声明外部函数 `extern void delay_us(uint32_t us);` 或者在一个公共头文件中声明并在 `main.c` 中包含该头文件。
但是,根据代码结构,我注意到在 `main.c` 中已经定义了 `delay_us` 函数,而编译时又链接了 `delay.o`(该文件来自 `delay.c` 的编译),所以导致了重复定义。
因此,我们可以选择:
选项1:删除 `main.c` 中的 `delay_us` 函数定义,使用 `delay.c` 中的函数。并在 `main.c` 中添加外部声明。
选项2:如果 `delay.c` 中的 `delay_us` 函数不是必需的,可以删除 `delay.c` 中的定义,只保留 `main.c` 中的定义。
这里,我假设项目中的 `delay.c` 文件是专门用来提供延时函数的,因此我们采用选项1。
修改步骤:
1. 在 `main.c` 中移除 `delay_us` 函数的定义(即删除函数体)。
2. 在 `main.c` 中添加外部声明:`extern void delay_us(uint32_t us);`
3. 确保 `delay.c` 中的 `delay_us` 函数实现正确。
但是,由于我们之前没有看到 `delay.c` 的内容,我们可以根据需求调整。如果项目中没有其他文件使用 `delay.c`,也可以考虑不使用它,而只保留 `main.c` 中的定义。但为了遵循模块化设计,建议将延时函数放在独立的文件中。
由于问题中只给出了 `main.c` 的代码,我推测你可能在项目中添加了 `delay.c` 文件,并且该文件中也定义了 `delay_us` 函数。
为了快速解决问题,我建议修改 `main.c`,移除其中的 `delay_us` 函数,并添加外部声明。同时,确保 `delay.c` 文件中的函数实现正确。
但是,如果项目中没有使用 `delay.c` 文件,那么你应该在工程中移除该文件,以避免链接冲突。
由于我无法得知你的整个工程结构,我将提供两个方案:
方案一:不使用独立的delay.c,只使用main.c中的delay_us
- 从工程中移除delay.c文件(如果存在)
- 确保main.c中的delay_us函数被保留
方案二:使用独立的delay.c,main.c中只保留声明
- 在main.c中删除delay_us函数定义(即删除代码末尾的delay_us函数体)
- 在main.c文件顶部添加外部声明:`extern void delay_us(uint32_t us);`
- 确保delay.c文件中有正确的delay_us实现(例如,使用系统时钟进行精确延时)
由于在之前的代码中,我们使用了简单的循环延时,如果项目要求精确延时,可能需要调整。但这里我们只解决重复定义问题。
我选择方案二,因为模块化更好。修改后的main.c如下:
注意:以下代码中移除了delay_us的定义,并添加了外部声明。</think>您的编译错误是由于`delay_us`函数在`main.c`和`delay.c`中重复定义造成的。以下是修正后的解决方案:
### 修正后的完整代码 (main.c)
```c
#include "stm32f10x.h"
// 函数声明
void USART1_Init(void);
void I2C1_Init(void);
void USART1_SendChar(char c);
void USART1_SendString(char *str);
void USART1_SendHex(uint8_t value);
void I2C1_Start(void);
void I2C1_Stop(void);
uint8_t I2C1_Address_Test(uint8_t addr);
void Delay_Init(void);
void delay_ms(uint32_t ms);
void delay_us(uint32_t us);
int main(void) {
// 系统时钟初始化(使用内部8MHz HSI)
RCC->CR |= RCC_CR_HSION; // 开启HSI
while(!(RCC->CR & RCC_CR_HSIRDY));// 等待HSI就绪
FLASH->ACR |= FLASH_ACR_PRFTBE; // 使能预取缓冲区
FLASH->ACR &= ~FLASH_ACR_LATENCY;
FLASH->ACR |= FLASH_ACR_LATENCY_2;// 2等待状态(48MHz)
RCC->CFGR |= RCC_CFGR_HPRE_DIV1; // AHB不分频
RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; // APB1分频系数2
RCC->CFGR |= RCC_CFGR_PLLMULL6; // PLL倍频x6 (8MHz * 6 = 48MHz)
RCC->CFGR &= ~RCC_CFGR_PLLSRC; // 使用HSI/2作为PLL输入源
RCC->CR |= RCC_CR_PLLON; // 开启PLL
while(!(RCC->CR & RCC_CR_PLLRDY));// 等待PLL就绪
RCC->CFGR |= RCC_CFGR_SW_PLL; // 切换PLL为系统时钟
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // 等待切换完成
// 初始化延时函数
Delay_Init();
// 外设初始化
USART1_Init();
I2C1_Init();
USART1_SendString("I2C Device Scanner\r\n");
uint8_t found = 0;
for(uint8_t addr = 1; addr < 128; addr++) { // 扫描所有I2C地址
if(I2C1_Address_Test(addr)) {
USART1_SendString("Device found at 0x");
USART1_SendHex(addr);
USART1_SendString("\r\n");
found = 1;
}
delay_us(100); // 地址间短暂延时
}
if(!found) USART1_SendString("No I2C devices found\r\n");
while(1);
}
// 串口1初始化(115200, 8N1)
void USART1_Init(void) {
// 使能时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_USART1EN;
// 配置PA9为复用推挽输出(TX)
GPIOA->CRH &= ~(GPIO_CRH_CNF9 | GPIO_CRH_MODE9);
GPIOA->CRH |= GPIO_CRH_CNF9_1 | GPIO_CRH_MODE9_0 | GPIO_CRH_MODE9_1;
// 配置USART
USART1->BRR = 48000000 / 115200; // 波特率设置
USART1->CR1 |= USART_CR1_UE | USART_CR1_TE; // 使能USART和发送器
}
// I2C1初始化(标准模式100kHz)
void I2C1_Init(void) {
// 使能时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
// 配置PB6(SCL)和PB7(SDA)为开漏输出
GPIOB->CRL &= ~(GPIO_CRL_CNF6 | GPIO_CRL_MODE6 | GPIO_CRL_CNF7 | GPIO_CRL_MODE7);
GPIOB->CRL |= GPIO_CRL_CNF6_1 | GPIO_CRL_MODE6 | GPIO_CRL_CNF7_1 | GPIO_CRL_MODE7;
// I2C配置
I2C1->CR2 = 48; // 设置APB1时钟频率(MHz)
I2C1->CCR = 240; // 100kHz标准模式: 48MHz/(2*100kHz)=240
I2C1->TRISE = 49; // 最大上升时间(48MHz*1000ns +1)
I2C1->CR1 |= I2C_CR1_PE; // 使能I2C
}
// 发送单个字符
void USART1_SendChar(char c) {
while(!(USART1->SR & USART_SR_TXE)); // 等待发送缓冲区空
USART1->DR = c;
}
// 发送字符串
void USART1_SendString(char *str) {
while(*str) USART1_SendChar(*str++);
}
// 发送十六进制值
void USART1_SendHex(uint8_t value) {
const char hexChars[] = "0123456789ABCDEF";
USART1_SendChar(hexChars[value >> 4]); // 高4位
USART1_SendChar(hexChars[value & 0x0F]); // 低4位
}
// I2C起始条件
void I2C1_Start(void) {
I2C1->CR1 |= I2C_CR1_START | I2C_CR1_ACK;
while(!(I2C1->SR1 & I2C_SR1_SB)); // 等待起始位完成
(void)I2C1->SR1; // 清除标志
}
// I2C停止条件
void I2C1_Stop(void) {
I2C1->CR1 |= I2C_CR1_STOP;
while(I2C1->CR1 & I2C_CR1_STOP); // 等待停止完成
}
// 测试I2C地址是否存在
uint8_t I2C1_Address_Test(uint8_t addr) {
uint8_t ack = 0;
I2C1_Start();
// 发送设备地址(写模式)
I2C1->DR = addr << 1;
// 超时计数器防止死锁
uint32_t timeout = 100000;
while(timeout--) {
if(I2C1->SR1 & I2C_SR1_ADDR) { // 收到应答
ack = 1;
(void)I2C1->SR1; // 清除ADDR标志
I2C1->SR2; // 读取SR2清除标志
break;
}
if(I2C1->SR1 & I2C_SR1_AF) { // 收到NACK
I2C1->SR1 &= ~I2C_SR1_AF; // 清除错误标志
break;
}
}
I2C1_Stop();
return ack;
}
// 延时函数初始化
void Delay_Init(void) {
// 使用SysTick定时器进行精确延时
SysTick->CTRL = 0; // 禁用SysTick
SysTick->LOAD = 0xFFFFFF; // 设置重载值为最大值
SysTick->VAL = 0; // 清除当前值
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk; // 使用处理器时钟
}
// 微秒级延时(基于SysTick)
void delay_us(uint32_t us) {
SysTick->LOAD = 48 * us; // 48MHz时钟
SysTick->VAL = 0; // 清除当前值
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // 启动定时器
while((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == 0); // 等待计数完成
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // 停止定时器
}
// 毫秒级延时
void delay_ms(uint32_t ms) {
while(ms--) delay_us(1000);
}
```
### 新增的 delay.c 文件(如果使用单独的延时模块)
```c
#include "stm32f10x.h"
// 延时函数初始化
void Delay_Init(void) {
// 使用SysTick定时器进行精确延时
SysTick->CTRL = 0; // 禁用SysTick
SysTick->LOAD = 0xFFFFFF; // 设置重载值为最大值
SysTick->VAL = 0; // 清除当前值
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk; // 使用处理器时钟
}
// 微秒级延时(基于SysTick)
void delay_us(uint32_t us) {
SysTick->LOAD = 48 * us; // 48MHz时钟
SysTick->VAL = 0; // 清除当前值
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // 启动定时器
while((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == 0); // 等待计数完成
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // 停止定时器
}
// 毫秒级延时
void delay_ms(uint32_t ms) {
while(ms--) delay_us(1000);
}
```
### 错误分析和解决方案
1. **问题分析**:
- 错误:`Symbol delay_us multiply defined (by main.o and delay.o)`
- 原因:`delay_us`函数同时在`main.c`和`delay.c`中定义
- 解决方案:
- 方案1:只在`main.c`中保留延时函数(如上所示)
- 方案2:将延时函数移到单独的`delay.c`文件中,并从`main.c`中移除
2. **优化改进**:
- 使用SysTick定时器替代简单的NOP循环,实现更精确的延时
- 添加毫秒级延时函数`delay_ms`
- 增加延时初始化函数`Delay_Init()`
- 使用标准库定义的寄存器位掩码(如`SysTick_CTRL_CLKSOURCE_Msk`)
3. **SysTick延时原理**:
```c
void delay_us(uint32_t us) {
SysTick->LOAD = 48 * us; // 设置重载值(48MHz时钟)
SysTick->VAL = 0; // 清除当前值
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // 启动定时器
while((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == 0); // 等待计数完成
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // 停止定时器
}
```
- 基于48MHz系统时钟,每个时钟周期约20.83ns
- 设置LOAD寄存器为所需延时的时钟周期数
- 使用COUNTFLAG标志检测计时完成
### 项目结构调整建议
1. **文件结构**:
```
project/
├── Core/
│ ├── main.c
│ ├── delay.c // 可选,如果使用单独的延时模块
│ └── delay.h
├── Drivers/
│ └── CMSIS/
└── ...
```
2. **delay.h 头文件内容**:
```c
#ifndef __DELAY_H
#define __DELAY_H
#include "stm32f10x.h"
void Delay_Init(void);
void delay_us(uint32_t us);
void delay_ms(uint32_t ms);
#endif
```
3. **使用说明**:
- 如果选择方案1:直接使用上面的`main.c`文件
- 如果选择方案2:
- 将延时函数移到`delay.c`
- 在`main.c`中包含`#include "delay.h"`
- 从`main.c`中移除`delay_us`函数定义
- 确保工程中只包含一个`delay_us`实现
### 编译和烧录步骤
1. 创建新工程,选择STM32F103C8T6设备
2. 添加启动文件`startup_stm32f10x_md.s`
3. 添加`main.c`(和可选的`delay.c`、`delay.h`)
4. 配置编译器选项,包含CMSIS头文件路径
5. 编译并烧录到开发板
6. 连接串口调试工具(115200波特率)
阅读全文
相关推荐




















