目录
1、前言
2、效果演示
(1)串口助手发送“led on”点亮LED
(2)串口助手发送“led off”熄灭LED
(3)串口助手发送“set led duty:”控制亮度
3、程序
(1)中断接收函数
(2)usart.c增加程序
(3)APP程序
4、体会
很多时候我们都要用到串口来控制程序的运行模式,例如通过串口助手向单片机发送命令,控制LED,PWM,调节PID参数等。
本章以点灯为例,单片机为STM32H750,其他单片机同样适用,实现了通过串口助手发送命令控制LED的开关,亮度。
发送数据时记得加上换行。
LED亮度变暗
首先是处理单片机串口接收到的数据,中断函数如下:
/* 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型的数组,当收到的数据以换行符结束时,保留本次接收的数据,清空上一次接收的数据。
增加一个字符串查找函数,一个字符串提取函数。
/* 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 */
用法示例如下,如果串口接收缓冲区中找到相应字符则返回子字符串的位置,否则返回-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中间可包含空格
这样的操作看起来显得不是那么专业,因为常见的数据传输都是帧头+命令字+数据包+校验+帧尾,但是这种形式一是不太直观,二是当命令多起来时需要定义较多的数据命令处理函数。
我写的这种方案确实是很方便,但是数据过于透明,不太适用于商用,各有各的好处,能满足需求就行。