C 字符串合法帧

这篇博客介绍了一道编程题,要求输入字符串和帧头帧尾,找出其中合法的帧。例如,在字符串"asdheadhauboisoktail"中,"headhauboisoktail"是一个合法帧,因为它的两端分别由"head"和"tail"标记。提供的C语言程序能够识别出这样的合法帧。

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

题目:输入一个字符串,同时输入帧头和帧尾(可以是多个字符),将该字符串中合法的帧识别出来.

提示:帧头和帧尾分别是head和tail  字符串”asdheadhauboisoktail”中headhauboisoktail是合法帧

思路:通过字符串匹配子串bf算法查找帧头和帧尾,输出合法帧

程序如下:

#include<stdio.h>

#include<string.h>


void legalstr(char * str, char *head,char *tail,int headl,int taill);
char * s_gets(char *st, int n);


int main()
{
char str1[40];
char head[20];
char tail[20];
int headl,taill;
printf("输入长度小于40的字符串:");
s_gets(str1, 40);
printf("输入长度小于20的帧头:");
s_gets(head,20);
headl = strlen(head);
printf("输入长度小于20的帧尾:");
s_gets(tail, 20);
taill = strlen(tail);
legalstr(str1, head,tail,headl,taill);
return 0;
}


char * s_gets(char * st, int n)
{
char * ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
while (st[i] != '\n'&&st[i] != '\0')
i++;
if (st[i] == '\n')
st[i] = '\0';
else
while (getchar() != '\n')
continue;
}
return ret_val;
}


void legalstr(char * str, char *head, char *tail, int headl, int taill)
{
int i, j;
int hlocat, tlocat;
i = 0;
j = 0;
while (str[i] != '\0')
{
if (str[i] == head[j])
{
i++;
j++;
}
else
{
i = i - j + 1;
j = 0;
}
if (j == headl)
{
hlocat = i - j;
j = 0;
break;
}
}
while (str[i] != '\0')
{
if (str[i] == tail[j])
{
i++;
j++;
}
else
{
i = i - j + 1;
j = 0;
}
if (j == taill)
{
tlocat = i;
j = 0;
}
}
printf("合法帧:");
for (i = hlocat; i < tlocat; i++)
printf("%c", str[i]);
printf("\n");

}

结果:

输入长度小于40的字符串:asdasdq23qwedqwdzzcx
输入长度小于20的帧头:asd
输入长度小于20的帧尾:qwd
合法帧:asdasdq23qwedqwd
请按任意键继续. . .

<think>我们正在处理串口通信中的数据接收问题。用户希望判断接收到的数据是否为字符串。根据引用[1]和引用[2],我们可以看到两种不同的处理方式:一种是轮询标志位,另一种是使用中断。引用[1]展示了轮询RXNE标志位(接收数据寄存器非空)来接收一个字节的数据。引用[2]展示了在串口中断服务函数中接收数据,并将接收到的字节存储到缓冲区中。引用[3]提到,在接收字符串时,需要考虑如何判断字符串接收完成,并指出不同方式对控制字符的处理。用户的问题:如何判断串口接收的数据是否为字符串。分析:串口通信本身传输的是字节流,所谓“字符串”通常是指可打印字符(ASCII码在32~126之间)组成的序列,并且以某种方式结束(例如以换行符'\n'或空字符'\0'结束)。因此,判断接收的数据是否为字符串,可以从以下两个方面考虑:1.检查每个字节是否都是可打印字符(或包含一些控制字符如换行符、回车符等,这些在字符串中常用)。2.判断字符串的结束标志。然而,串口通信中,我们通常需要定义一个协议来确定字符串的结束。常见的方式有:-使用特定的结束符(例如换行符'\n')。-使用固定长度(但这样如果传输内容不足需要填充)。-使用超时机制(即两个字符之间的时间间隔超过一定值就认为一结束)。因此,我们可以采用以下步骤:1.在接收数据时,将数据存入缓冲区。2.检查缓冲区中的数据是否都是可打印字符(或者允许的特定控制字符,如回车、换行等)。3.根据协议判断字符串是否接收完整(例如是否接收到结束符)。根据引用[2],他们在中断中接收数据,但没有判断结束条件,而是固定最多接收10个字节。引用[3]提到了通过控制字符(如0x770x04)作为结束标志。因此,我们可以设计:-在中断服务函数中,每次接收到一个字符就存入缓冲区,并检查是否接收到结束符(比如换行符'\n')。如果接收到结束符,则设置一个标志位表示字符串接收完成。-在主循环中,检查这个标志位,如果置位,则处理缓冲区中的数据(检查是否为字符串,即每个字节是否都是可打印字符或允许的控制字符)。另一种方法:在接收数据时,每收到一个字节就检查它是否是可打印字符(或允许的字符),如果不是,则可以认为不是字符串(或者根据协议,非字符串数据可能包含不可打印字符,这样就需要根据协议来区分)。但是,通常我们不会在接收过程中实时判断是否为字符串,而是先接收完整的一数据(根据结束符或超时),然后再判断这一数据是否为字符串。具体实现思路:1.定义接收缓冲区和一个索引,以及一个完成标志。2.在串口中断中,每次接收一个字节,将其放入缓冲区,并检查是否为结束符(例如'\n')。如果是,则设置完成标志,并将索引清零(或记录位置)。同时,也要防止缓冲区溢出。3.在主循环中,如果完成标志被置位,则遍历缓冲区,检查每个字节是否都是可打印字符(ASCII32~126)或者是允许的控制字符(如'\r'、'\n'、'\t'等)。如果都是,则认为接收的是字符串;否则,不是字符串。注意:我们也可以不检查每个字节,而只是根据结束符来判断字符串结束,然后将其作为字符串处理(即使中间有不可打印字符,也可以认为是一串字节,但这样可能就不是可读字符串了)。所以,用户所说的“字符串”应该是指可读的文本字符串。因此,我们可以在主循环中这样判断:示例代码(基于引用[1]和引用[2]的风格,使用STM32和C51的混合参考,具体需要根据平台调整):我们以STM32为例(使用HAL库或标准库),但这里使用类似引用[1]的标准库方式:步骤:1.初始化串口,开启接收中断(或者使用轮询方式,但中断更常见)。2.在中断中接收数据并存入缓冲区,直到遇到结束符。3.在主循环中检查完成标志,然后判断缓冲区内容是否为字符串。由于引用[1]是轮询方式,我们可以改为中断方式。但为了简单,这里给出中断方式的示例。定义全局变量:```c#defineBUFFER_SIZE64charrx_buffer[BUFFER_SIZE];//接收缓冲区uint8_trx_index=0;//当前缓冲区索引volatileuint8_trx_complete=0;//接收完成标志,由中断设置,主循环清除```串口中断服务函数(以USART2为例):```cvoidUSART2_IRQHandler(void){if(USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET){//读取一个字节charc=USART_ReceiveData(USART2);//如果收到结束符(这里用换行符'\n')if(c=='\n'){//在缓冲区末尾添加结束符(如果需要,也可以添加'\0',但这里我们只是标记完成)if(rx_index<BUFFER_SIZE-1){rx_buffer[rx_index++]=c;//可以再加一个'\0',这样缓冲区就可以当作C字符串使用//rx_buffer[rx_index]='\0';//如果需要,这里先不加,因为后面要判断是否为可打印字符,而'\0'不可打印}rx_complete=1;//设置完成标志rx_index=0;//重置索引,为下一次接收做准备(注意:这里重置前,主循环应该已经处理了数据,或者使用双缓冲)}else{//不是结束符,则存储if(rx_index<BUFFER_SIZE-1){rx_buffer[rx_index++]=c;}else{//缓冲区满了,但还没遇到结束符,可以选择设置完成标志并重置,或者丢弃//这里我们选择设置标志,表示接收了一(虽然没结束符,但满了)rx_complete=1;rx_index=0;}}}}```注意:上面的中断函数中,我们使用了换行符作为结束符。另外,在缓冲区满时也设置完成标志。这样,主循环可以处理两种情况:正常结束(有结束符)和非正常结束(缓冲区满)。在主循环中:```cwhile(1){if(rx_complete){//先清除标志rx_complete=0;//现在检查rx_buffer中的数据是否为字符串(可打印字符)uint8_tis_string=1;for(inti=0;i<rx_index;i++){//检查每个字符:可打印字符的ASCII范围是32~126,以及一些控制字符(如回车'\r'=13,换行'\n'=10,制表符'\t'=9等)if(rx_buffer[i]=='\n'||rx_buffer[i]=='\r'||rx_buffer[i]=='\t'){//这些控制字符允许出现在字符串中continue;}if(rx_buffer[i]<32||rx_buffer[i]>126){is_string=0;break;}}if(is_string){//处理字符串//例如,发送回串口for(inti=0;i<rx_index;i++){Serial_SendByte(rx_buffer[i]);//假设Serial_SendByte是发送一个字节的函数}}else{//不是字符串,可能是二进制数据,按其他方式处理}//处理完后,重置rx_index(注意:中断中已经重置了,这里不需要?)//但是注意,我们在中断里每次完成一就重置rx_index为0,所以这里不需要再重置。//但如果需要重新开始接收,中断中已经重置了索引,所以这里不用操作。}}```注意:上面的中断函数中,在收到结束符后重置rx_index为0,这样下一次接收就会覆盖之前的缓冲区。如果主循环处理时间较长,可能会被中断覆盖,因此更安全的做法是使用双缓冲区。但为了简单,这里用单缓冲区,且主循环处理速度很快。另外,我们允许的控制字符只有'\n'、'\r'、'\t',可以根据需要添加。但是,上面的处理有一个问题:我们在中断中重置了rx_index,而主循环中使用了rx_index(在循环中作为长度),这可能导致主循环处理时rx_index已经被重置为0(而实际数据长度应该是之前的)。因此,我们需要在中断中不重置rx_index,而是在主循环中重置。修改中断函数(不再重置rx_index):```cvoidUSART2_IRQHandler(void){if(USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET){charc=USART_ReceiveData(USART2);if(c=='\n'){if(rx_index<BUFFER_SIZE-1){rx_buffer[rx_index++]=c;}rx_complete=1;//设置完成标志//不再这里重置rx_index,让主循环处理}else{if(rx_index<BUFFER_SIZE-1){rx_buffer[rx_index++]=c;}else{//缓冲区满,设置完成标志rx_complete=1;}}}}```主循环修改:```cwhile(1){if(rx_complete){rx_complete=0;//清除标志//保存当前接收到的数据长度uint8_tlen=rx_index;//重置rx_index,为下一次接收做准备(注意:此时中断可能还在接收,所以需要谨慎)//为了避免中断和主循环同时操作rx_index,我们可以暂时关闭中断,但这里简单处理,先重置,因为中断中只会在rx_complete为0时才会重新开始接收(但中断不会等待主循环,所以需要保护)//更安全的方式:使用两个缓冲区,或者先停止接收中断,处理完再开启。//这里为了简单,我们使用临界区(关闭中断)__disable_irq();//关闭总中断(根据具体MCU调整,这里用伪代码)rx_index=0;__enable_irq();//开启中断//现在处理缓冲区,长度为lenuint8_tis_string=1;for(inti=0;i<len;i++){//检查每个字符if(rx_buffer[i]=='\n'||rx_buffer[i]=='\r'||rx_buffer[i]=='\t'){continue;}if(rx_buffer[i]<32||rx_buffer[i]>126){is_string=0;break;}}if(is_string){//处理字符串for(inti=0;i<len;i++){Serial_SendByte(rx_buffer[i]);}}else{//非字符串处理}}}```注意:关闭中断的操作会影响系统实时性,如果接收频率不高,可以接受。或者使用双缓冲机制:一个缓冲区用于中断接收,另一个用于主循环处理。当完成标志置位时,交换缓冲区。双缓冲示例(简略):定义两个缓冲区,和一个当前使用的缓冲区索引(或指针)。中断中往当前缓冲区写,当完成一时,设置完成标志,并切换到另一个缓冲区。主循环处理完成标志,并处理非当前接收的缓冲区(即已经完成接收的缓冲区)。由于时间关系,这里不展开双缓冲。总结:判断串口接收的数据是否为字符串,需要先接收完整的一(通过结束符或超时),然后检查中的每个字节是否都是可打印字符或允许的控制字符。根据引用[3],我们也可以使用超时机制来判断结束,这样就不需要结束符了。例如,使用定时器,每次接收到一个字符就重置定时器,如果定时器超时,则认为一结束。这种方法可以处理任意字符,包括二进制数据。但判断是否为字符串的方法同上。因此,用户可以根据实际需求选择结束判断方式(结束符或超时),然后对接收到的进行内容检查。相关问题:1.串口通信中,如何判断一数据接收完成?2.如何设计串口通信协议来区分字符串和二进制数据?3.在嵌入式系统中,如何安全地处理串口接收缓冲区以避免数据覆盖?</think>### 如何判断串口接收的数据是否为字符串 在嵌入式系统中判断串口接收的数据是否为字符串,主要涉及两个关键点:**结束判定**和**内容有效性验证**。以下是具体实现方法: --- #### **1. 结束判定方法** 需要先确定何时完成一数据的接收,常用方法有: ##### **(1) 结束符检测法** 当检测到预定义的结束符(如换行符`\n`、回车符`\r`或自定义字符)时,认为一结束。 **代码示例**(基于引用[2]的中断方式): ```c #define END_CHAR '\n' // 定义结束符 volatile uint8_t rx_buffer[64]; // 接收缓冲区 volatile uint8_t rx_index = 0; // 缓冲区索引 volatile bool frame_complete = false; // 完成标志 void USART_IRQHandler(void) { if (USART_GetFlagStatus(USARTx, USART_FLAG_RXNE)) { uint8_t data = USART_ReceiveData(USARTx); // 读取数据 if (data == END_CHAR) { // 检测结束符 rx_buffer[rx_index] = '\0'; // 添加字符串终止符 frame_complete = true; // 标记完成 } else if (rx_index < sizeof(rx_buffer)-1) { rx_buffer[rx_index++] = data; // 存储数据 } } } ``` ##### **(2) 超时判定法** 使用定时器检测字符间隔时间(如超过5ms无新数据),适用于无固定结束符的场景(引用[3]): ```c // 在中断中重置超时计时器 void USART_IRQHandler(void) { if (USART_GetFlagStatus(USARTx, USART_FLAG_RXNE)) { rx_buffer[rx_index++] = USART_ReceiveData(USARTx); TIM_ResetCounter(TIMx); // 重置超时计时器 } } // 定时器中断中检测超时 void TIM_IRQHandler(void) { if (TIM_GetITStatus(TIMx, TIM_IT_Update)) { if (rx_index > 0) { // 有未处理数据 rx_buffer[rx_index] = '\0'; // 终止字符串 frame_complete = true; // 标记完成 } TIM_ClearITPendingBit(TIMx, TIM_IT_Update); } } ``` --- #### **2. 字符串有效性验证** 完成后,检查内容是否为合法字符串: ```c bool is_valid_string(const uint8_t* data, uint32_t len) { for (uint32_t i = 0; i < len; i++) { // 检查是否为可打印ASCII字符(32~126)或常用控制符 if (!(data[i] == '\r' || data[i] == '\n' || (data[i] >= 32 && data[i] <= 126))) { return false; // 发现非法字符 } } return true; // 全部字符合法 } // 主循环中调用 while (1) { if (frame_complete) { if (is_valid_string(rx_buffer, rx_index)) { // 处理字符串(如打印或解析) printf("Received: %s\n", rx_buffer); } else { // 处理二进制数据(如协议解析) } frame_complete = false; // 重置标志 rx_index = 0; // 清空缓冲区 } } ``` --- #### **关键注意事项** 1. **缓冲区安全** - 防止溢出:在中断中检查`rx_index < buffer_size-1` - 临界区保护:操作全局变量时关闭中断(如`__disable_irq()`) 2. **特殊协议处理** 若数据包含二进制协议(如引用[3]的0x77 0x04结束符),需在结束检测中兼容混合内容。 3. **编码兼容性** 非ASCII系统(如UTF-8)需调整字符校验逻辑。 4. **实时性优化** 对长数据使用双缓冲机制:中断填充后台缓冲区,主循环处理前台缓冲区。 --- ### 相关问题 1. 如何区分字符串数据和二进制协议数据? 2. 串口接收超时时间如何根据波特率动态调整? 3. 多字节字符(如中文)在串口通信中如何处理? 4. 环形缓冲区和双缓冲哪种更适合高速串口通信? 5. 如何设计协议头/尾防止数据与指令混淆? > 实现时请参考具体硬件平台手册调整寄存器操作,如STM32的`USART_FLAG_RXNE`或C51的`RI`标志[^1][^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值