学习STM32 CubeMX实验7:使用USART进行printf打印和串口接收特定数据包
一、printf的实现
学过C语言的同学都知道printf函数,是打开C语言大门的一个函数,如何在stm32上使用该函数呢!
要在stm32上使用printf函数,需要对printf进行重定向,把串口发送的数据通过printf打印出来
//printf重定向代码(可添加到usart_it.c或者其他地方)
#include <stdio.h>
int fputc(int ch,FILE *p) //函数默认的,在使用printf函数时自动调用
{
USART1->DR = (uint8_t) ch;//发送
while((USART1->SR & 0X40) == 0);//等待发送完成
return ch;
}
对printf重定向后,避免使用半主机模式二导致库函数程序无法运行,使用微库Micro LIB可以避免半主机模式,在keil中点击Options for Target…,勾选Use Micro LIB,点击OK即可。
避免使用半主机模式还有一种方法是定义 _sys_exit(),可以在不使用Micro LIB(取消勾选Use Micro LIB)的情况避免半主机模式。
#if 1
#pragma import(__use_no_semihosting) // 确保没有从 C 库链接使用半主机的函数
int fputc(int ch,FILE *p) //函数默认的,在使用printf函数时自动调用
{
USART1->DR = (uint8_t) ch;//发送
while((USART1->SR & 0X40) == 0);//等待发送完成
return ch;
}
FILE __stdout;
struct __FILE
{
int handle;
};
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x=x;
}
#endif
在main循环中添加printf打印函数,即可观察上位机到上位机每100ms打印一次数据。
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/*为了观看方便,把头文件放到这里*/
#include "usart_it.h"
#include <stdio.h>
/*为了观看方便,把头文件放到这里*/
// //开启串口接收中断,每次接收到一个字节后中断一次
// HAL_UART_Receive_IT(&huart1,&USART1_Stru.receive,1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
static uint8_t i=0;
i++;
printf("%d\r\n",i);
HAL_Delay(100);
}
/* USER CODE END 3 */
}
二、数据包接收
当要使用串口上位机控制单片机时,可以通过发送一个字节进行控制,但是在实际应用中可能需要传输一组数据,这时发送一个字节就无法控制了,需要发送一组数据,同时修改控制参数,要怎么接收这一组数据呢!
可以定义一个数据帧,通过该帧格式获取每个数据的信息,如在调试PID时要控制三个参数,通过串口上位机发送该数据帧就能调节PID参数。在该例子中可以定义帧格式为:
0xcc 0xpid_p 0xpid_i 0xpid_d 0xeb 0x90
帧头 数据1 数据2 数据3 帧尾
接收到数据后,先判断是否接收到帧尾的两个字节,当收到一帧数据后,通过帧头进行检验,校验通过后将收到的数据帧回传到串口上位机,通过该代码可以解析出一帧数据dat1/dat2/dat3。接收代码如下:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart==&huart1)
{
//读取接收到的数据
USART1_Stru.RxBuff[USART1_Stru.RxLen] = USART1_Stru.receive;
//帧尾
if (USART1_Stru.RxBuff[USART1_Stru.RxLen] == 0x90\
&& USART1_Stru.RxBuff[USART1_Stru.RxLen - 1] == 0xeb)
{
//帧头
if (USART1_Stru.RxBuff[0] == 0xCC)
{
//将收到的数据帧发送回上位机,也可以将数据复制到其他地方,方便调用
HAL_UART_Transmit(&huart1,USART1_Stru.RxBuff,USART1_Stru.RxLen+1,1000);
}
USART1_Stru.RxLen = 0;
}
else
{
USART1_Stru.RxLen++;
}
//需要再次开启接收中断,否则只进入一次
HAL_UART_Receive_IT(&huart1,&USART1_Stru.receive,1);
}
}
全部代码如下:
#include "usart_it.h"
#include <stdio.h>
#if 1
#pragma import(__use_no_semihosting) // 确保没有从 C 库链接使用半主机的函数
int fputc(int ch,FILE *p) //函数默认的,在使用printf函数时自动调用
{
USART1->DR = (uint8_t) ch;//发送
while((USART1->SR & 0X40) == 0);//等待发送完成
return ch;
}
FILE __stdout;
struct __FILE
{
int handle;
};
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x=x;
}
#endif
USART1_Structure USART1_Stru = { 0 };
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart==&huart1)
{
//读取接收到的数据
USART1_Stru.RxBuff[USART1_Stru.RxLen] = USART1_Stru.receive;
//帧尾
if (USART1_Stru.RxBuff[USART1_Stru.RxLen] == 0x90\
&& USART1_Stru.RxBuff[USART1_Stru.RxLen - 1] == 0xeb)
{
//帧头
if (USART1_Stru.RxBuff[0] == 0xCC)
{
//将收到的数据帧发送回上位机,也可以将数据复制到其他地方,方便调用
HAL_UART_Transmit(&huart1,USART1_Stru.RxBuff,USART1_Stru.RxLen+1,1000);
}
USART1_Stru.RxLen = 0;
}
else
{
USART1_Stru.RxLen++;
}
//需要再次开启接收中断,否则只进入一次
HAL_UART_Receive_IT(&huart1,&USART1_Stru.receive,1);
}
}
#ifndef _USART_IT_H
#define _USART_IT_H
#include "main.h"
#include "usart.h"
//最大接收数据长度
#define USART1RxLEN 200
typedef struct
{
uint8_t receive;
uint8_t RxBuff[USART1RxLEN];
uint8_t RxLen;
}USART1_Structure;
extern USART1_Structure USART1_Stru;
#endif
//main函数中只需要添加接收中断代码
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/*为了观看方便,把头文件放到这里*/
#include "usart_it.h"
#include <stdio.h>
/*为了观看方便,把头文件放到这里*/
//开启串口接收中断,每次接收到一个字节后中断一次
HAL_UART_Receive_IT(&huart1,&USART1_Stru.receive,1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
// static uint8_t i=0;
// i++;
// printf("%d\r\n",i);
// HAL_Delay(100);
}
/* USER CODE END 3 */
}
已经实现了printf打印和数据包的接收功能,下期一起学习USART-DMA接收不定长度字节的数据。
作者:单片kun