STM32串口提取数据和命令字符

目录

1、前言

2、效果演示

        (1)串口助手发送“led on”点亮LED

        (2)串口助手发送“led off”熄灭LED

        (3)串口助手发送“set led duty:”控制亮度

3、程序

        (1)中断接收函数

        (2)usart.c增加程序

        (3)APP程序

 4、体会


1、前言

        很多时候我们都要用到串口来控制程序的运行模式,例如通过串口助手向单片机发送命令,控制LED,PWM,调节PID参数等。

        本章以点灯为例,单片机为STM32H750,其他单片机同样适用,实现了通过串口助手发送命令控制LED的开关,亮度。

2、效果演示

        发送数据时记得加上换行

        (1)串口助手发送“led on”点亮LED

STM32串口提取数据和命令字符_第1张图片

STM32串口提取数据和命令字符_第2张图片

        (2)串口助手发送“led off”熄灭LED

STM32串口提取数据和命令字符_第3张图片

STM32串口提取数据和命令字符_第4张图片

                

        (3)串口助手发送“set led duty:”控制亮度

STM32串口提取数据和命令字符_第5张图片

                 LED亮度变暗

STM32串口提取数据和命令字符_第6张图片

 

3、程序

        (1)中断接收函数

            首先是处理单片机串口接收到的数据,中断函数如下:

/* USER CODE BEGIN 4 */

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  static uint16_t usart1_rx_counter = 0; // 接收计数器  
  UNUSED(huart); //消除警告
  	
	if(usart1_rx_counter < UART1_RX_LENGTH)  
		{
			usart1_receive_buffer[usart1_rx_counter++] = rx1_temp_char;   //接收数据转存
			if(rx1_temp_char == '\n')
			{
			 //将'\n'(usart1_rx_counter)后面的数据清空; usart1_receive_buffer[0] = NULL表示将整个数组清空
			 usart1_receive_buffer[usart1_rx_counter] = NULL;
			 usart1_rx_counter = 0;	
			 //printf("%s",usart1_receive_buffer);  //测试语句,不需要可以删除
			}
		}	
	 HAL_UART_Receive_IT(&huart1, (uint8_t *)&rx1_temp_char, 1);   //再开启接收中断

}
/* USER CODE END 4 */

主要作用是将接收到的数据存到一个uint8_t型的数组,当收到的数据以换行符结束时,保留本次接收的数据,清空上一次接收的数据。

        (2)usart.c增加程序

        增加一个字符串查找函数,一个字符串提取函数。

/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "string.h"
#include "stdbool.h"
#include 
#include 

/* USER CODE END Includes */

uint8_t usart1_receive_buffer[UART1_RX_LENGTH] = {0}; // 接收缓冲区  
uint8_t rx1_temp_char = 0; // 用于存储接收到的单个字符
/* USER CODE BEGIN 1 */
/**
 * @brief 在给定文本中查找模式字符串的首次匹配位置。
 *
 * @param text 要搜索的文本字符串。
 * @param pattern 要查找的子字符串。
 * @return 如果找到匹配,返回匹配的起始位置(索引);如果未找到匹配,返回 -1。
 */
int strFound(unsigned char *text, unsigned char *pattern) {
    // 手动计算文本长度
    int text_len = 0;
    while (text[text_len] != '\0') {
        text_len++;
    }
    // 手动计算模式长度
    int pattern_len = 0;
    while (pattern[pattern_len] != '\0') {
        pattern_len++;
    }
    // 遍历文本
    for (int i = 0; i <= text_len - pattern_len; i++) {
        int j;
        // 检查模式是否匹配
        for (j = 0; j < pattern_len; j++) {
            if (text[i + j] != pattern[j]) {
                break;
            }
        }
        // 如果完整匹配
        if (j == pattern_len) {
            return i; // 返回匹配的位置
        }
    }
    return -1; // 未找到匹配
}

/**
 * @brief 从给定文本中提取一个整数,跳过模式字符串及前面的空白字符。
 *
 * @param text 要提取数字的文本字符串。
 * @param pattern 要查找的模式字符串。
 * @return 如果成功提取,返回提取的整数值;如果未找到有效的数字,返回 0。
 */
int str2num(unsigned char *text, unsigned char *pattern) {
    int start_position = strFound(text, pattern);
    
    // 如果未找到模式,返回 0
    if (start_position == -1) {
        return 0;
    }

    // 手动计算模式长度
    int pattern_len = 0;
    while (pattern[pattern_len] != '\0') {
        pattern_len++;
    }

    // 从匹配位置的下一个字符开始查找数字
    int k = start_position + pattern_len;

    // 跳过空白字符
    while (text[k] != '\0' && isspace(text[k])) {
        k++;
    }

    // 检查后面是否有数字
    if (text[k] != '\0') {
        char *endptr;
        // 检查是否为负号
        int sign = 1;
        if (text[k] == '-') {
            sign = -1; // 记录为负数
            k++; // 跳过负号
        }

        // 检查是否有数字
        if (text[k] != '\0' && isdigit(text[k])) {
            return sign * (int)strtol((char *)&text[k], &endptr, 10); // 提取数字并应用符号
        }
    }
    return 0; // 提取失败
}
/* USER CODE END 1 */
        (3)APP程序

        用法示例如下,如果串口接收缓冲区中找到相应字符则返回子字符串的位置,否则返回-1。

/* USER CODE BEGIN Header_APP1 */
/**
  * @brief  Function implementing the name_APP1 thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_APP1 */
void APP1(void *argument)
{
  /* USER CODE BEGIN APP1 */
  /* Infinite loop */
	bool led_state = 0;
	int led_duty = 0;
  for(;;)
  {
		if(strFound(usart1_receive_buffer,"led on")!=-1)  led_state = 1;
		if(strFound(usart1_receive_buffer,"led off")!=-1) led_state = 0;
		 
		if(led_state)
		{
			led_duty = str2num(usart1_receive_buffer,"set led duty:");
		 	HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
			osDelay(led_duty);
			HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
			osDelay(20-led_duty);
			printf("duty = %d\n",led_duty);
		}
		else
			 HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
//			 usart1_receive_buffer[0] = NULL;		
//		printf("hello bug\n");
    osDelay(1);
  }
  /* USER CODE END APP1 */
}

        led_duty = str2num(usart1_receive_buffer,"set led duty:")作用是将接收到的“set led duty: 19”中的数字提取出来,即提取关键字符串后的字符数字并转化为Int型数据,关键字可自定义。

        例如:

//在第一个字符串中匹配是否含有第二个子字符串,如果匹配成功,且后面带有数字,则提取相应的值
current = str2num("set current = 500 ma","set current ="); //返回500
current = str2num("set current = -500 ma","set current ="); //返回-500
current = str2num("set motor current = 500 ma","set current ="); //返回0,因为源字符串不包含"set current ="

current = str2num("set current =          -500 ma","set current ="); //返回-500中间可包含空格

 4、体会

        这样的操作看起来显得不是那么专业,因为常见的数据传输都是帧头+命令字+数据包+校验+帧尾,但是这种形式一是不太直观,二是当命令多起来时需要定义较多的数据命令处理函数。

        我写的这种方案确实是很方便,但是数据过于透明,不太适用于商用,各有各的好处,能满足需求就行。

你可能感兴趣的:(STM32相关,stm32,嵌入式硬件,单片机)