*** Using Compiler 'V5.06 update 5 (build 528)', folder: 'E:\Keil_v5\ARM\ARMCC\Bin' Build target 'Target 1' assembling startup_stm32f10x_md.s... compiling misc.c... compiling stm32f10x_dma.c... compiling core_cm3.c... compiling stm32f10x_crc.c... compiling stm32f10x_adc.c... compiling stm32f10x_dac.c... compiling stm32f10x_can.c... compiling stm32f10x_cec.c... compiling stm32f10x_bkp.c... compiling system_stm32f10x.c... compiling stm32f10x_exti.c... compiling stm32f10x_dbgmcu.c... compiling stm32f10x_iwdg.c... compiling stm32f10x_fsmc.c... compiling stm32f10x_gpio.c... compiling stm32f10x_flash.c... compiling stm32f10x_i2c.c... compiling stm32f10x_pwr.c... compiling stm32f10x_rcc.c... compiling stm32f10x_sdio.c... compiling stm32f10x_rtc.c... compiling stm32f10x_spi.c... compiling stm32f10x_tim.c... compiling stm32f10x_usart.c... compiling Key.c... compiling stm32f10x_wwdg.c... compiling PWM.c... compiling Motor.c... compiling stm32f10x_it.c... linking... .\Objects\Project.axf: error: L3921U: Option '--library' not available with your license. Finished: 0 information, 0 warning, 0 error and 1 fatal error messages. ".\Objects\Project.axf" - 1 Error(s), 0 Warning(s). Target not created. Build Time Elapsed: 00:00:01
时间: 2025-07-19 15:25:15 浏览: 9
<think>我们遇到了一个链接错误:`error: L3921U: Option '--library' not available with your license.`
这个错误通常是由于Keil MDK的许可证问题导致的,特别是当我们尝试使用`--library`选项来链接数学库`libm.lib`时。
解决方案:
1. 检查Keil的许可证,确保它支持使用该选项(通常完整版才支持)。
2. 如果不支持,我们可以尝试不使用数学库,而是使用其他方法避免浮点运算,或者使用软件浮点库(但效率较低)。
3. 另一种方法是使用MicroLIB,它内置了浮点运算支持,但功能有限。
由于我们使用了浮点运算(如`logf`,`fabsf`等),我们需要链接数学库。如果许可证不允许,我们可以尝试以下替代方案:
替代方案1:使用MicroLIB
- 在Keil中:Options for Target -> Target -> 勾选"Use MicroLIB"
- MicroLIB提供了基本的数学函数,但可能不支持所有标准库函数。
替代方案2:避免使用浮点运算
- 将浮点运算转换为定点运算,例如使用整数运算来近似计算温度。
- 这需要重新设计温度计算函数。
替代方案3:使用软件浮点库(不依赖硬件FPU)
- 在Keil中,可以设置使用软件浮点库,但需要确保许可证支持。
根据错误信息,我们的许可证不支持`--library`选项,因此我们尝试使用MicroLIB。
步骤:
1. 在Keil中打开Options for Target对话框(Alt+F7)。
2. 转到Target选项卡。
3. 在Code Generation区域,勾选“Use MicroLIB”。
4. 重新编译。
如果使用MicroLIB后仍然需要数学函数,MicroLIB提供了一些基本的数学函数,但可能不包括`logf`。因此,我们可能需要自己实现一个简单的对数函数,或者使用查找表。
考虑到热敏电阻的计算,我们可以采用以下方法之一:
方法1:使用查找表(LUT)
- 预先计算ADC值到温度的映射表(整数形式),然后通过查表获取温度。
方法2:使用整数运算近似
- 将浮点运算转换为整数运算,例如使用定点数(Q格式)进行计算。
由于我们的ADC是12位(0-4095),我们可以预先计算一个长度为4096的查找表,这样可以直接通过ADC值得到温度(整数表示,如乘以10避免小数)。
这里我们采用查找表方法:
步骤:
1. 在程序初始化时,预先计算一个温度查找表(或者直接定义为常量数组)。
2. 温度计算函数改为查表。
注意:查找表会占用一定的内存(4096个整数,约8KB),如果内存紧张,可以降低精度(如每隔4个点取一个值,然后插值)。
考虑到STM32F103有20KB的RAM,使用8KB是可以接受的。
查找表实现:
```c
// 定义查找表(全局常量)
const int16_t Temp_LUT[4096]; // 温度乘以10(即25.3°C存储为253)
// 初始化查找表(在程序开始前计算好)
void Init_Temp_LUT(void) {
for (uint16_t adc_val = 0; adc_val < 4096; adc_val++) {
// 跳过0和4095(避免除0)
if (adc_val == 0 || adc_val >= 4095) {
Temp_LUT[adc_val] = -1000; // 表示无效值,实际使用时跳过
continue;
}
float voltage = (adc_val * V_REF) / 4095.0f;
float rt = R_REF * (V_REF / voltage - 1.0f);
float steinhart = 1.0f / (T0) + (1.0f / BETA) * logf(rt/R0);
float temp = (1.0f / steinhart) - 273.15f;
// 限制范围并乘以10(转换为整数)
if (temp < -10) temp = -10;
if (temp > 100) temp = 100;
Temp_LUT[adc_val] = (int16_t)(temp * 10);
}
}
// 在main函数中调用Init_Temp_LUT一次
```
然后在计算温度时:
```c
Temperature = (float)Temp_LUT[adc_avg] / 10.0f;
```
但是,这样仍然在查找表的初始化中使用了浮点运算,而且每次启动都要计算,不合适。
因此,我们可以将查找表预先计算好,定义为常量数组(在ROM中),这样就不需要运行时计算。
我们可以写一个简单的程序(在PC上)生成这个数组,然后直接复制到代码中。
例如,生成数组的Python代码:
```python
import math
R0 = 10000.0
T0 = 298.15
BETA = 3950.0
R_REF = 10000.0
V_REF = 3.3
# 生成4096个点的查找表
table = []
for adc_val in range(0, 4096):
if adc_val == 0 or adc_val >= 4095:
table.append(-1000) # 无效值
continue
voltage = (adc_val * V_REF) / 4095.0
rt = R_REF * (V_REF / voltage - 1.0)
steinhart = 1.0 / T0 + (1.0 / BETA) * math.log(rt / R0)
temp = (1.0 / steinhart) - 273.15
if temp < -10:
temp = -10
if temp > 100:
temp = 100
table.append(int(temp * 10))
# 输出C数组
print("const int16_t Temp_LUT[4096] = {")
for i in range(0, 4096, 16): # 每行16个
line = ', '.join(f'{val:5d}' for val in table[i:i+16])
print(f" {line},")
print("};")
```
然后将生成的数组复制到代码中。
这样,我们就完全避免了在嵌入式代码中使用浮点运算和数学库。
修改后的温度计算函数:
```c
float Calculate_Temperature(uint16_t adc_val) {
if (adc_val == 0 || adc_val >= 4095) return 0;
int16_t temp_int10 = Temp_LUT[adc_val]; // 温度乘以10
return (float)temp_int10 / 10.0f;
}
```
注意:由于我们已经在查找表中限制了范围,所以不需要再次限制。
另外,在显示温度时,我们也可以直接使用整数运算:
```c
// 显示温度
static float last_temp = -100;
if (fabsf(last_temp - Temperature) > 0.1f) {
last_temp = Temperature;
int temp_int = (int)temp_int10 / 10; // 整数部分
int temp_frac = temp_int10 % 10; // 小数部分(绝对值)
if (temp_frac < 0) temp_frac = -temp_frac; // 确保小数部分为正
OLED_ShowNum(3, 7, temp_int, 2);
OLED_ShowChar(3, 9, '.');
OLED_ShowNum(3, 10, temp_frac, 1);
}
```
但是注意,如果温度为负,则整数部分为负,小数部分为正。所以我们需要特殊处理负温度。
更好的方法是:在查找表中存储的是温度乘以10的整数,所以我们可以这样显示:
```c
int16_t temp_int10 = Temp_LUT[adc_avg];
if (temp_int10 < 0) {
OLED_ShowChar(3, 7, '-');
temp_int10 = -temp_int10;
} else {
OLED_ShowChar(3, 7, '+'); // 或者空格,根据需求
}
int temp_int = temp_int10 / 10;
int temp_frac = temp_int10 % 10;
OLED_ShowNum(3, 8, temp_int, 2); // 注意位置调整
OLED_ShowChar(3, 10, '.');
OLED_ShowNum(3, 11, temp_frac, 1);
```
但我们的显示区域有限,我们之前是在第3行第7列开始显示,格式为"XX.X",所以如果为负数,我们需要显示负号,并调整数字位置。
修改显示:
```c
static float last_temp = -100;
if (fabsf(last_temp - Temperature) > 0.1f) {
last_temp = Temperature;
int16_t temp_int10 = Temp_LUT[adc_avg];
int abs_temp = temp_int10;
if (temp_int10 < 0) {
abs_temp = -temp_int10;
OLED_ShowChar(3, 7, '-');
} else {
OLED_ShowChar(3, 7, ' '); // 正数显示空格(覆盖负号)
}
int temp_int = abs_temp / 10;
int temp_frac = abs_temp % 10;
OLED_ShowNum(3, 8, temp_int, 2);
OLED_ShowChar(3, 10, '.');
OLED_ShowNum(3, 11, temp_frac, 1);
}
```
这样,我们完全避免了在嵌入式代码中使用浮点运算和数学库,因此不再需要链接数学库。
重新编译,应该不会出现链接错误。
总结修改:
1. 使用查找表将ADC值映射到温度(整数,乘以10)。
2. 在PC上生成查找表数组,并嵌入代码中。
3. 修改温度计算函数,直接查表并转换为浮点数(或者也可以直接使用整数形式,但显示部分需要调整)。
4. 修改温度显示部分,适应整数形式。
注意:查找表会占用4096*2=8192字节(8KB)的Flash空间。
如果Flash空间紧张,可以考虑以下方法:
- 降低查找表精度(如每4个点存一个,然后使用线性插值)。
- 使用公式计算,但避免浮点运算(使用定点数运算)。
但考虑到STM32F103C8T6有64KB Flash,8KB是可以接受的。
现在,我们修改代码,避免使用浮点运算和数学库,从而避免链接数学库。
修改后的main.c代码框架如下(包含部分查找表):</think>根据编译错误信息,问题在于Keil许可证不支持`--library`选项。这通常是由于使用了免费版本的MDK(MDK-Lite)导致的限制。让我们解决这个问题并提供一个完整的解决方案。
### 解决方案:避免使用外部数学库
我们将重构代码,完全避免使用数学库函数(如`logf`, `fabsf`等),改用整数运算和查找表实现温度计算。
#### 修改后的main.c
```c
#include "stm32f10x.h"
#include "OLED.h"
#include "AD.h"
#include "Delay.h"
#include "Key.h"
/* 全局变量定义 */
int8_t Speed = 0;
int16_t Temperature = 0; // 温度值乘以10(用于整数运算)
uint8_t Mode = 0;
/* 热敏电阻参数 - 转换为整数运算 */
#define R0 10000
#define T0 29815 // 298.15 * 100
#define BETA 3950
#define R_REF 10000
#define V_REF 33 // 3.3V * 10
/* 温度计算查找表 */
const int16_t Temp_LUT[] = {
// ADC值到温度的映射(温度值乘以10)
// 这里只展示部分值,实际需要完整4096个点的映射
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// ... 实际应用中需要完整的4096个点映射 ...
250, 255, 260, 265, 270, 275, 280, 285, 290, 295, 300, 305, 310, 315, 320, 325
};
/* 温度计算函数 - 使用查找表 */
int16_t Calculate_Temperature(uint16_t adc_val) {
if(adc_val >= sizeof(Temp_LUT)/sizeof(Temp_LUT[0]))
return 0;
return Temp_LUT[adc_val];
}
/* OLED行清除函数 */
void OLED_ClearLine(uint8_t line, uint8_t start_col) {
char clear_str[20];
for(uint8_t i = 0; i < sizeof(clear_str)-1; i++)
clear_str[i] = ' ';
clear_str[sizeof(clear_str)-1] = '\0';
OLED_ShowString(line, start_col, clear_str);
}
int main(void) {
// 外设初始化
OLED_Init();
AD_Init();
Key_GPIO_Init();
Delay_Init();
OLED_Clear();
OLED_ShowString(1, 1, "Mode: ");
OLED_ShowString(2, 1, "Speed: ");
OLED_ShowString(3, 1, "Temp: ");
OLED_ShowString(3, 11, "C");
OLED_ShowString(4, 1, "ADC: ");
// 初始化显示
OLED_ShowChar(1, 7, Mode ? 'M' : 'A');
OLED_ShowSignedNum(2, 7, Speed, 3);
OLED_ShowNum(3, 7, 0, 2);
OLED_ShowChar(3, 9, '.');
OLED_ShowNum(3, 10, 0, 1);
OLED_ShowNum(4, 6, 0, 4);
while (1) {
/* 温度采集 - 滑动窗口平均滤波 */
static uint16_t adc_buffer[5] = {0};
static uint8_t index = 0;
adc_buffer[index] = AD_GetValue();
index = (index + 1) % 5;
uint32_t adc_sum = 0;
for(uint8_t i=0; i<5; i++) adc_sum += adc_buffer[i];
uint16_t adc_avg = adc_sum / 5;
/* 温度计算 */
Temperature = Calculate_Temperature(adc_avg);
/* 温度范围限制 */
if(Temperature < -100) Temperature = -100; // -10.0°C
if(Temperature > 1000) Temperature = 1000; // 100.0°C
/* 模式切换 - 按键2 */
static uint8_t k2_last = 1;
if(k2_last && !KEY2_DOWN()) {
Mode ^= 1;
OLED_ClearLine(1, 7);
}
k2_last = KEY2_DOWN();
/* 速度设置 */
if(Mode == 0) {
Speed = (int8_t)(Temperature / 10); // 温度转为速度
} else {
static uint8_t k1_last = 1;
if(k1_last && !KEY1_DOWN()) {
Speed = (Speed + 10) % 100;
OLED_ClearLine(2, 7);
}
k1_last = KEY1_DOWN();
}
/* OLED刷新 - 每200ms更新一次 */
static uint32_t last_refresh = 0;
if(Delay_Time() - last_refresh >= 200) {
last_refresh = Delay_Time();
// 更新模式显示
OLED_ShowChar(1, 7, Mode ? 'M' : 'A');
// 更新速度显示
static int8_t last_speed = -1;
if(last_speed != Speed) {
last_speed = Speed;
OLED_ShowSignedNum(2, 7, Speed, 3);
}
// 更新温度显示
static int16_t last_temp = -1000;
if(last_temp != Temperature) {
last_temp = Temperature;
// 显示整数部分
int8_t temp_int = Temperature / 10;
uint8_t is_negative = 0;
if(temp_int < 0) {
is_negative = 1;
temp_int = -temp_int;
}
// 显示小数部分
uint8_t temp_frac = Temperature % 10;
if(temp_frac < 0) temp_frac = -temp_frac;
// 显示温度
OLED_ShowChar(3, 7, is_negative ? '-' : ' ');
OLED_ShowNum(3, 8, temp_int, 2);
OLED_ShowChar(3, 10, '.');
OLED_ShowNum(3, 11, temp_frac, 1);
}
// 更新ADC值显示
static uint16_t last_adc = 0;
if(last_adc != adc_avg) {
last_adc = adc_avg;
OLED_ShowNum(4, 6, adc_avg, 4);
}
}
// 延时5ms
Delay_ms(5);
}
}
```
### 关键修改说明
1. **完全避免数学库使用**:
- 移除了所有`#include <math.h>`和`#include <stdlib.h>`
- 使用整数运算代替浮点运算
- 使用查找表代替对数计算
2. **温度值表示方式**:
```c
int16_t Temperature = 0; // 温度值乘以10(25.5°C表示为255)
```
- 使用整数表示温度,避免浮点运算
- 温度范围:-100到1000(对应-10.0°C到100.0°C)
3. **温度计算查找表**:
```c
const int16_t Temp_LUT[] = {
// ADC值到温度的映射
0, 0, 0, ... 250, 255, 260, ...
};
```
- 预先计算好ADC值到温度的映射
- 实际应用中需要完整的4096个点映射
4. **温度显示优化**:
```c
// 分离整数和小数部分
int8_t temp_int = Temperature / 10;
uint8_t temp_frac = Temperature % 10;
// 显示处理
OLED_ShowChar(3, 7, is_negative ? '-' : ' ');
OLED_ShowNum(3, 8, temp_int, 2);
OLED_ShowChar(3, 10, '.');
OLED_ShowNum(3, 11, temp_frac, 1);
```
- 完全使用整数运算处理温度显示
- 正确处理负温度显示
5. **延时函数适配**:
```c
// 使用Delay库提供的计时函数
if(Delay_Time() - last_refresh >= 200) {
// 刷新逻辑
}
```
- 假设Delay库提供了`Delay_Time()`函数返回系统tick
### 查找表生成工具(Python)
由于需要完整的4096点温度映射,可以使用Python生成查找表:
```python
import math
def generate_temp_lut():
R0 = 10000.0
T0 = 298.15 # 25°C in Kelvin
BETA = 3950.0
R_REF = 10000.0
V_REF = 3.3
lut = []
for adc_val in range(4096):
if adc_val == 0 or adc_val >= 4095:
lut.append(0)
continue
voltage = (adc_val * V_REF) / 4095.0
rt = R_REF * (V_REF / voltage - 1.0)
try:
steinhart = 1.0 / T0 + (1.0 / BETA) * math.log(rt / R0)
temp_c = (1.0 / steinhart) - 273.15
# 温度值乘以10并转换为整数
temp_int = int(temp_c * 10)
# 限制范围 -10.0°C 到 100.0°C
if temp_int < -100: temp_int = -100
if temp_int > 1000: temp_int = 1000
except:
temp_int = 0
lut.append(temp_int)
return lut
# 生成并输出C数组
lut = generate_temp_lut()
print("const int16_t Temp_LUT[4096] = {")
for i in range(0, 4096, 16): # 每行16个值
line = ", ".join(f"{val:5d}" for val in lut[i:i+16])
print(f" {line},")
print("};")
```
### Keil配置调整
1. **移除数学库链接**:
- 打开Options for Target → Linker
- 移除Misc controls中的`--library=libm.lib`
2. **禁用浮点支持**:
- Options for Target → Target
- 取消勾选"Use MicroLIB"(如果不需要)
- 在C/C++选项卡中,Preprocessor Symbols添加`__CC_ARM`(如果使用ARMCC)
3. **优化设置**:
- C/C++ → Optimization设置为-O1或-O2
- 勾选"One ELF Section per Function"
### 备选方案:软件浮点实现
如果必须使用浮点运算,可以实现简化版的数学函数:
```c
// 简化版自然对数函数(适用于热敏电阻计算范围)
float simple_log(float x) {
// 使用泰勒级数近似
if (x <= 0) return 0;
float term = (x - 1) / (x + 1);
float term2 = term * term;
float result = term;
float power = term;
for (int i = 1; i < 5; i++) {
power *= term2;
result += power / (2 * i + 1);
}
return 2 * result;
}
```
### 工程结构建议
```
Project/
├── Core/
│ └── main.c # 主程序
├── Drivers/
│ ├── AD.c # ADC驱动
│ ├── OLED.c # OLED驱动
│ ├── Delay.c # 延时库
│ └── Key.c # 按键驱动
├── Inc/
│ ├── main.h
│ ├── AD.h
│ ├── OLED.h
│ ├── Delay.h
│ └── Key.h
└── Libraries/ # ST标准外设库
```
阅读全文
相关推荐



















