1.strstr字符
strstr(command, "$cmd,get ant para") 的作用是:
在字符串 command 中查找是否包含子串 "$cmd,get ant para"。
函数原型一般是:
复制代码
cs
char *strstr(const char *str1, const char *str2);
含义:
str1:被查找的字符串,这里就是 command
str2:要查找的子串,这里是 "$cmd,get ant para
返回值:如果找到了,返回子串第一次出现的位置指针如果没找到,返回 NULL
2.sscanf
cs
/* 从命令中提取单个增益值 */
char *comma1 = strstr(command, "rx");
if (comma1 == NULL ||
sscanf(comma1 + 2, ",%lf", &rx_gain_db) != 1 ||
rx_gain_db < 0.0 || rx_gain_db > 30.0)
{
Cmd_Reply("$cmd,set convert gain,rx,ack error\r\n");
return;
}
strstr(command, "rx")在command里查找子串"rx"。- 如果找到了,返回指向
'r'的指针; - 如果没找到,返回
NULL
cs
sscanf(comma1 + 2, ",%lf", &rx_gain_db) != 1 ||
-
comma1 + 2:跳过"rx"这两个字符,指向紧跟在"rx"后面的内容。 -
格式化字符串
",%lf"的意思是:- 先匹配一个逗号
,; - 然后读取一个浮点数,存入
rx_gain_db中。
- 先匹配一个逗号
-
sscanf的返回值是"成功赋值的变量个数"。这里只有%lf一个变量,所以成功时必须返回 1。 -
如果
"rx"后面不是,数字这种格式,比如是"rxabc"或"rx abc",sscanf会失败,返回值不等于 1,进入错误处理。
3.大端序
cs
void XW_On_SetBDC_Attenuation(uint16_t att_01db)
{
/* 对应 $cmd,down convert 3 */
uint16_t value = ((float)att_01db / 100.0f) * 10;
actuallyDownGain = att_01db;
downConvertGain[8] = (value >> 8) & 0xFF;
downConvertGain[9] = value & 0xFF;
downConvertGain[10] = (CRC16_Calculate(downConvertGain, 10) >> 8) & 0xFF;
downConvertGain[11] = CRC16_Calculate(downConvertGain, 10) & 0xFF;
UART3_TX_DMA_Send(downConvertGain, sizeof(downConvertGain));
}
cs
downConvertGain[8] = (value >> 8) & 0xFF;
downConvertGain[9] = value & 0xFF;
这两句就是把 16 位的 value 拆成两个字节:
downConvertGain[8]存的是 高 8 位downConvertGain[9]存的是 低 8 位- 这种写法叫 大端序(Big-Endian,高字节在前)。先发高字节再发低字节
4.strncmp
功能:比较字符串前 n 个字符
cs
#include <string.h>
int strncmp(const char *s1, const char *s2, size_t n);
| 返回值 | 含义 |
|---|---|
0 |
s1 和 s2 的前 n 个字符完全相同 |
< 0 |
s1 的前 n 个字符小于 s2(按无符号字节值比较) |
> 0 |
s1 的前 n 个字符大于 s2 |
cs
static const char prefix[] = "$cmd,set ephem;";
if (command == NULL ||
strncmp(command, prefix, sizeof(prefix) - 1U) != 0) return 0;
p = command + sizeof(prefix) - 1U;
说明:
-
prefix是命令头:"$cmd,set ephem;"- 它的长度是 15 个字符;
sizeof(prefix)是 16(包含结尾的'\0');sizeof(prefix) - 1U就是 15,即不比较字符串结束符。
-
strncmp(command, prefix, 15) != 0- 判断
command的开头 15 个字节是否正好等于"$cmd,set ephem;"; - 这就是典型的前缀匹配。
- 判断
5.strtol字符串转long整数(string to long)
原型和头文件
cs
#include <stdlib.h>
long strtol(const char *nptr, char **endptr, int base);
功能
把 nptr 指向的字符串的起始部分 ,按指定进制 base 转换成 long 类型整数。
转换流程:
-
跳过前面的空白字符(空格、
\t、\n等); -
读取可选的正负号:
+或-; -
读取符合
base进制的数字字符; -
遇到第一个"不属于该进制数字"的字符时停止;
-
如果
endptr不是NULL,就把第一个无效字符的地址 存入*endptr。在你的代码中,全部使用:
csstrtol(p, &end, 10);表示明确按十进制解析,这样即使字符串是
"08",也会被当作合法的十进制 8,不会被误判为八进制。endptr机制:这是strtol最关键的用法
csstrtol(p, &end, 10); -
转换结果存入
count-end指向数字后面的第一个字符。 -
如果字符串是:复制代码
"20;20260906,1230,..."那么:
-
count = 20 -
end指向后面的;
6.strtod(string to double)
函数原型
cs
#include <stdlib.h>
double strtod(const char *nptr, char **endptr);
二、参数说明
| 参数 | 含义 |
|---|---|
nptr |
指向要解析的字符串,例如 "15.5,123.45,45.67;" |
endptr |
二级指针。函数会把"第一个无法继续解析的字符地址"存入 *endptr。如果不关心结束位置,可以传 NULL,但做协议解析时通常都要用 |
三、返回值
| 情况 | 返回值 |
|---|---|
| 成功解析 | 对应的 double 浮点数值 |
| 没有解析到任何数字 | 返回 0.0,并且 *endptr == nptr(即 end == p) |
| 正数溢出 | 返回 HUGE_VAL,并设置 errno = ERANGE |
| 负数溢出 | 返回 -HUGE_VAL,并设置 errno = ERANGE |
| 下溢 | 返回 0.0 或很小的非规格化数,某些实现会设置 ERANGE |
7.snprintf
cs
/* 把 frame(22B) 和 resp(8B) 拼成一行:"AA 55 ... | 00 00 ..." */
static void BeamHexLine(char *out, const uint8_t *frame, const uint8_t *resp)
{
uint16_t pos = 0;
uint8_t i;
for (i = 0U; i < 22U; i++)
pos += (uint16_t)snprintf(out + pos, BEAM_HEX_BUF - pos, "%02X", frame[i]);
pos += (uint16_t)snprintf(out + pos, BEAM_HEX_BUF - pos, ",resp=");
for (i = 0U; i < 8U; i++)
pos += (uint16_t)snprintf(out + pos, BEAM_HEX_BUF - pos, "%02X", resp[i]);
out[pos] = '\0';
}
- 函数签名
cs
static void BeamHexLine(char *out, const uint8_t *frame, const uint8_t *resp)
out:输出字符串的缓冲区(char 数组)frame:指向 22 字节数据的指针,通常是发送给天线的控制帧resp:指向 8 字节数据的指针,通常是天线返回的响应帧
- 核心语法:
snprintf
snprintf 的格式是:
cs
int snprintf(char *str, size_t size, const char *format, ...);
- 它把格式化后的字符串写入
str size是当前缓冲区剩余可写的最大字符数 (包括结尾的\0)- 返回值是"如果空间足够,实际会写入的字符数 ",也就是格式化结果的长度(不包括结尾的
\0)
注意:如果 size 不够大,snprintf 会截断,但返回值仍然是"如果空间足够的长度",而不是实际写入的长度。
- 变量
pos的作用
pos 记录"已经写到了缓冲区 out 的哪个位置",也就是当前偏移量。
每执行一次 snprintf,返回值是本次写入的字符数,把它累加到 pos 上,下一次写入就从新的位置开始。
类似用一个光标向后移动:
cs
char buf[100];
int pos = 0;
pos += snprintf(buf + pos, 100 - pos, "Hello");
pos += snprintf(buf + pos, 100 - pos, " World");
执行后 buf 是 "Hello World",pos 是 11。
- 逐行解析
cs
uint16_t pos = 0; uint8_t i;
pos初始为 0,表示从out的第一个字节开始写i是循环变量,用uint8_t(0~255)足够,因为循环次数最多 22
第一段循环:写 frame 的 22 个字节
cs
for (i = 0U; i < 22U; i++) pos += (uint16_t)snprintf(out + pos, BEAM_HEX_BUF - pos, "%02X", frame[i]);
frame[i]是一个字节(0~255)"%02X"表示把这个字节格式化为两位大写十六进制 ,不足两位前面补0- 比如 0x0A 输出
"0A",0x3F 输出"3F"
- 比如 0x0A 输出
out + pos是当前写入位置BEAM_HEX_BUF - pos是剩余缓冲区大小,防止溢出snprintf返回 2(因为每个字节固定输出 2 个字符),所以pos每次加 2
执行完这个循环后,out 的前 44 个字符就是 22 个字节的十六进制表示。
中间:写分隔符
cs
pos += (uint16_t)snprintf(out + pos, BEAM_HEX_BUF - pos, ",resp=");
- 在刚才的十六进制串后面追加
",resp=",共 6 个字符 pos加 6
第二段循环:写 resp 的 8 个字节
cs
for (i = 0U; i < 8U; i++) pos += (uint16_t)snprintf(out + pos, BEAM_HEX_BUF - pos, "%02X", resp[i]);
- 同理,把 8 个字节写成 16 个字符
- 每次
pos加 2
最后补结束符
cs
out[pos] = '\0';
snprintf每次都会在写入的字符串末尾自动加\0,但最后一次之后,我们为了确保整个字符串安全结束,再手动在当前位置放一个\0- 这一步是保险,防止缓冲区末尾没有结束符导致越界读取
- 总体效果
假设:
cs
frame = {0xAA, 0x55, 0x01, ..., 0x00} (22字节)
resp = {0x00, 0x11, ..., 0xFF} (8字节)
则生成的字符串是:
cs
AA5501...00,resp=0011...FF