本文为串口输出打印的hal库,参考洋桃电子的入门30步总结而来。
打开core文件夹下src文件夹,找到syscalls.c文件将其排除编译。因为syscalls.c文件与我们即将添加的文件内容相冲突。
在core——inc中增加retarget.h文件;在在core——src中增加retarget.c文件
retarget.h文件
#ifndef INC_RETARGET_H_
#define INC_RETARGET_H_
#include "stm32f1xx_hal.h"
#include "stdio.h"//用于printf函数串口重映射
#include
void RetargetInit(UART_HandleTypeDef *huart);
int _isatty(int fd);
int _write(int fd, char* ptr, int len);
int _close(int fd);
int _lseek(int fd, int ptr, int dir);
int _read(int fd, char* ptr, int len);
int _fstat(int fd, struct stat* st);
#endif /* INC_RETARGET_H_ */
retarget.c文件
#include <_ansi.h>
#include <_syslist.h>
#include
#include
#include
#include
#include
#include <../Inc/retarget.h>
#include
#include
#if !defined(OS_USE_SEMIHOSTING)
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
UART_HandleTypeDef *gHuart;
void RetargetInit(UART_HandleTypeDef *huart) {
gHuart = huart;
/* Disable I/O buffering for STDOUT stream, so that
* chars are sent out as soon as they are printed. */
setvbuf(stdout, NULL, _IONBF, 0);
}
int _isatty(int fd) {
if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
return 1;
errno = EBADF;
return 0;
}
int _write(int fd, char* ptr, int len) {
HAL_StatusTypeDef hstatus;
if (fd == STDOUT_FILENO || fd == STDERR_FILENO) {
hstatus = HAL_UART_Transmit(gHuart, (uint8_t *) ptr, len, HAL_MAX_DELAY);
if (hstatus == HAL_OK)
return len;
else
return EIO;
}
errno = EBADF;
return -1;
}
int _close(int fd) {
if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
return 0;
errno = EBADF;
return -1;
}
int _lseek(int fd, int ptr, int dir) {
(void) fd;
(void) ptr;
(void) dir;
errno = EBADF;
return -1;
}
int _read(int fd, char* ptr, int len) {
HAL_StatusTypeDef hstatus;
if (fd == STDIN_FILENO) {
hstatus = HAL_UART_Receive(gHuart, (uint8_t *) ptr, 1, HAL_MAX_DELAY);
if (hstatus == HAL_OK)
return 1;
else
return EIO;
}
errno = EBADF;
return -1;
}
int _fstat(int fd, struct stat* st) {
if (fd >= STDIN_FILENO && fd <= STDERR_FILENO) {
st->st_mode = S_IFCHR;
return 0;
}
errno = EBADF;
return 0;
}
#endif //#if !defined(OS_USE_SEMIHOSTING)
在main.c文件中增加retarget.h文件,并开启printf串口打印功能。
usart.c
/*
* usart1.c
*
* Created on: Oct 20, 2021
* Author: Administrator
*/
#include "usart.h"
uint8_t USART1_RX_BUF[USART1_REC_LEN];//接收缓冲,最大USART_REC_LEN个字节.接收usart1串口接收的全部数据
uint16_t USART1_RX_STA=0;//接收状态标记//bit15:接收完成标志,bit14:接收到0x0d,bit13~0:接收到的有效字节数目。保存接收数据的数量和是否收到回车键
uint8_t USART1_NewData;//当前串口中断接收的1个字节数据的缓存,产生串口中断时,此变量存储最新收到的一个字符
uint8_t USART2_RX_BUF[USART2_REC_LEN];//接收缓冲,最大USART_REC_LEN个字节.
uint16_t USART2_RX_STA=0;//接收状态标记//bit15:接收完成标志,bit14:接收到0x0d,bit13~0:接收到的有效字节数目
uint8_t USART2_NewData;//当前串口中断接收的1个字节数据的缓存
uint8_t RS485orBT;//当RS485orBT标志位为1时是RS485模式,为0时是蓝牙模式
uint8_t USART3_RX_BUF[USART3_REC_LEN];//接收缓冲,最大USART_REC_LEN个字节.
uint16_t USART3_RX_STA=0;//接收状态标记//bit15:接收完成标志,bit14:接收到0x0d,bit13~0:接收到的有效字节数目
uint8_t USART3_NewData;//当前串口中断接收的1个字节数据的缓存
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//串口中断回调函数
{
if(huart ==&huart1)//判断中断来源(串口1:USB转串口)
{
printf("%c",USART1_NewData); //把收到的数据以 a符号变量 发送回电脑
if((USART1_RX_STA&0x8000)==0){//接收未完成,1000 0000 0000 0000
if(USART1_RX_STA&0x4000){//接收到了0x0d回车符,0100 0000 0000 0000
if(USART1_NewData!=0x0a)USART1_RX_STA=0;//接收错误,重新开始,0x0a表示换行符
else USART1_RX_STA|=0x8000; //接收完成了
}else{ //还没收到0X0D
if(USART1_NewData==0x0d)USART1_RX_STA|=0x4000;//将 USART1_RX_STA 的第14位设置为1,表示已经接收到回车符(0x0d)
else{
USART1_RX_BUF[USART1_RX_STA&0X3FFF]=USART1_NewData; //将收到的数据放入数组
USART1_RX_STA++; //数据长度计数加1
if(USART1_RX_STA>(USART1_REC_LEN-1))USART1_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1); //再开启接收中断
}
if(huart ==&huart2)//判断中断来源(RS485/蓝牙模块)
{
if(RS485orBT){//当RS485orBT标志位为1时是RS485模式,为0时是蓝牙模式
USART2_RX_BUF[0]=USART2_NewData;//将接收到的数据放入缓存数组(因只用到1个数据,所以只存放在数据[0]位置)
USART2_RX_STA++;//数据接收标志位加1
}else{
printf("%c",USART2_NewData); //把收到的数据以 a符号变量 发送回电脑
}
HAL_UART_Receive_IT(&huart2,(uint8_t *)&USART2_NewData, 1); //再开启接收中断
}
if(huart ==&huart3)//判断中断来源(串口3:WIFI模块)
{
printf("%c",USART3_NewData); //把收到的数据以 a符号变量 发送回电脑
HAL_UART_Receive_IT(&huart3,(uint8_t *)&USART3_NewData,1); //再开启接收中断
}
}
usart.h
/*
* usart1.h
*
* Created on: Oct 20, 2021
* Author: Administrator
*/
#ifndef INC_USART_H_
#define INC_USART_H_
#include "stm32f1xx_hal.h" //HAL库文件声明
#include //用于字符串处理的库
#include "../inc/retarget.h"//用于printf函数串口重映射
extern UART_HandleTypeDef huart1;//声明USART1的HAL库结构体
extern UART_HandleTypeDef huart2;//声明USART2的HAL库结构体
extern UART_HandleTypeDef huart3;//声明USART2的HAL库结构体
#define USART1_REC_LEN 200//定义USART1最大接收字节数
#define USART2_REC_LEN 200//定义USART1最大接收字节数
#define USART3_REC_LEN 200//定义USART1最大接收字节数
extern uint8_t USART1_RX_BUF[USART1_REC_LEN];//接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern uint16_t USART1_RX_STA;//接收状态标记
extern uint8_t USART1_NewData;//当前串口中断接收的1个字节数据的缓存
extern uint8_t USART2_RX_BUF[USART2_REC_LEN];//接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern uint16_t USART2_RX_STA;//接收状态标记
extern uint8_t USART2_NewData;//当前串口中断接收的1个字节数据的缓存
extern uint8_t RS485orBT;//当RS485orBT标志位为1时是RS485模式,为0时是蓝牙模式
extern uint8_t USART3_RX_BUF[USART3_REC_LEN];//接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern uint16_t USART3_RX_STA;//接收状态标记
extern uint8_t USART3_NewData;//当前串口中断接收的1个字节数据的缓存
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);//串口中断回调函数声明
#endif /* INC_USART_H_ */
串口接收有两种方法:查询法、中断法
查询法:在主循环中写一个if判断语句,循环判断串口接收标志位,因为usart1串口收到数据后,硬件会自动将接收标志位从0改为1。若标志位为1,则串口接收数据,会自动从对应的串口接收寄存器中读出数据。
中断法:开启nvic中断控制器中的usart1串口中断功能,当串口收到数据,硬件自动产生中断,单片机中止当前的程序,进入中断处理程序,读出接收寄存器中收到的数据。
在标准库中,是在中断处理函数中完成对接收数据的处理,当产生串口接收中断,程序会中止运行主函数,自动跳转到对应的中断函数。优点:简单直接,缺点:程序停留在中断处理函数的时间太久,会耽误主函数的时间,也会让其他中断事件受阻,其他中断需要等待当前中断处理完成后才能进行下一个中断的处理。
在HAL库中,为中断回调函数的方式处理中断事件。当产生中断事件,程序会先跳转到中断处理函数,此中断处理函数只标注了中断来源,然后快速退出中断,回到主函数,然后并不执行之前中止的程序,而是自动调用中断回调函数,对中断事件的处理都放在回调函数里。由于此时已经退出中断状态,回归主函数,所以其他中断不会受阻
中断回调函数还有一个弱函数定义的概念
这段代码是HAL库中UART接收完成回调函数的定义,当UART接收完成时,HAL库会自动调用这个函数。
__weak 关键字表示该函数是一个弱符号,允许用户在自己的代码中定义同名的函数以覆盖这个函数的默认实现。
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) 中的 UART_HandleTypeDef *huart 是指向 UART_HandleTypeDef 结构体的指针,它包含了与 UART 通信相关的参数和状态信息。
该回调函数在默认情况下是空函数,通过在函数体中调用 UNUSED(huart); 可以防止编译器产生未使用参数的警告。
注释中指出,如果需要使用回调函数,则应在用户文件中实现 HAL_UART_RxCpltCallback 函数。这个回调函数可以用来处理 UART 接收完成事件,例如读取接收缓冲区中的数据。
开启串口接收中断函数,这一行函数内容与usart.c文件第41行的内容一样。因为单片机上电启动时,串口接收中断默认关闭状态,所以需要先开启
主循环增加串口控制继电器的程序。只有判断有回车键,才会进行对串口接收数据的处理。
此时串口程数据已经在中断回调函数中处理过了,处理号的内容存放在usart.c文件的10、11行
以上内容主要让大家掌握串口通信方式,前期学习,只要先会用即可。