关于cubeIDE开发基本技巧及流程,本文不详细叙述,请参考:cubeIDE快速开发流程_py_free的博客-CSDN博客_cubeide汉化
一、stm32串口配置
本文采用的开发板是stm32L496VGT3,其有两个 USB 接口,一个为 USB ST-link 复用接口,作用为软件下载/调试/ 系统供电输入口。另一个为 USB OTG,用户可以外接 USB 设备,支持 1000mA,USB2.0 设备接入。本文就采用 USB ST-link口实现串口上下行通信及调试信息输出显示。
USB ST-Link(USB1) 接口的串口通信及调试信息需要我们映射实现。
打开cubeMX配置界面,配置串口映射,开启LPUART串口或其他USART*(1)的异步传输模式(2),配置串口参数(3),并将串口收发数据端口映射到PB10、PB11上(4),并开启中断功能(5),最好点击保存或生成代码按钮生成代码。
开启外部中断:
设置上拉输出:
二、串口驱动设计及代码实现
重新构建系统的printf函数,将其映射到LPUART串口。
【1】首先禁用CubeIDE生成syscall.c文件
【2】创建print.h和print.c两个驱动文件来取代syscall.c内的函数_read、_write等实现,本文是将这两个文件放置在ICore目录内
print.h内容如下:
/*
* print.h
*
* Created on: 2021年11月16日
* Author: Administrator
*/
#ifndef INC_RETARGET_H_
#define INC_RETARGET_H_
#include "stm32l4xx_hal.h"
#include "stdio.h"//用于printf函数串口重映射
#include <sys/stat.h>
void ResetPrintInit(UART_HandleTypeDef *huart);//将printf()函数映射到指定串口上
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_ */
print.c内容如下,主要是将syscall.c内的空实现函数通过串口实现数据收发处理
/*
* print.c
*
* Created on: 2021年11月16日
* Author: Administrator
*/
#include <_ansi.h>
#include <_syslist.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/times.h>
#include <limits.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include "print.h"
#if !defined(OS_USE_SEMIHOSTING)
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
UART_HandleTypeDef *gHuart;
void ResetPrintInit(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) {
//串口发送数据实现,可以main.c等功能代码中直接调用HAL_UART_Transmit也能实现串口发送数据
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)
【3】创建串口驱动接口,主要就是实现串口回调函数
前面print.h/c实现串口输出能力,但其输入能力由于lpusart开启了外部中断功能,还需我们实现其回调函数,因为cubeMX生成的HAL库(stm32l4xx_hal_uart.c,文件名因芯片不同有所区别)的串口接收回调函数是个弱声明函数,其实现源码没有做什么事。
现在创建usart.h、usart.c两个串口驱动文件,放置ICore目录下
usart.h内容如下,HAL_UART_RxCpltCallback会覆盖stm32l4xx_hal_uart.c的同名回调函数:
/*
* usart.h
*
* Created on: Oct 20, 2022
* Author: Administrator
*/
#ifndef INC_USART_H_
#define INC_USART_H_
#include "stm32l4xx_hal.h" //HAL库文件声明
#include <string.h>//用于字符串处理的库
#include "print.h"//用于printf函数串口重映射
extern UART_HandleTypeDef hlpuart1;//声明USART1的HAL库结构体
#define HLPUSART_REC_LEN 256//定义USART1最大接收字节数
extern uint8_t HLPUSART_RX_BUF[HLPUSART_REC_LEN];//接收缓冲,最大HLPUSART_REC_LEN个字节.末字节为换行符
extern uint16_t HLPUSART_RX_STA;//接收状态标记
extern uint8_t HLPUSART_NewData;//当前串口中断接收的1个字节数据的缓存
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);//串口中断回调函数声明
#endif /* INC_USART_H_ */
usart.c内容如下:
/*
* usart.c
*
* Created on: Oct 20, 2022
* Author: Administrator
*/
#include "usart.h"
uint8_t HLPUSART_RX_BUF[HLPUSART_REC_LEN];//接收缓冲,最大HLPUSART_REC_LEN个字节.末字节为换行符
/*
* bit15:接收到回车(0x0d)时设置HLPUSART_RX_STA|=0x8000;
* bit14:接收溢出标志,数据超出缓存长度时,设置HLPUSART_RX_STA|=0x4000;
* bit13:预留
* bit12:预留
* bit11~0:接收到的有效字节数目(0~4095)
*/
uint16_t HLPUSART_RX_STA=0;接收状态标记//bit15:接收完成标志,bit14:接收到回车(0x0d),bit13~0:接收到的有效字节数目
uint8_t HLPUSART_NewData;//当前串口中断接收的1个字节数据的缓存
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//串口中断回调函数
{
if(huart ==&hlpuart1)//判断中断来源(串口1:USB转串口)
{
if(HLPUSART_RX_STA&0x4000){//溢出,重新开始
HLPUSART_RX_STA=0;//接收错误,重新开始
}
if(HLPUSART_NewData==0x0d){//回车标记
printf("getdata:%.*s \r\n", HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);
HLPUSART_RX_STA|=0x8000;//标记接到回车
}else{
if((HLPUSART_RX_STA&0X0FFF)<HLPUSART_REC_LEN){
HLPUSART_RX_BUF[HLPUSART_RX_STA&0X0FFF]=HLPUSART_NewData; //将收到的数据放入数组
HLPUSART_RX_STA++; //数据长度计数加1
}else{
HLPUSART_RX_STA|=0x4000;//数据超出缓存长度,标记溢出
}
}
HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData,1); //再开启接收中断
}
}
三、串口驱动功能调用及编译实现
在原来的key.h/key.c中添加按键按下识别函数,
key.h:
/*
* key.h
*
* Created on: Sep 29, 2022
* Author: py_hp
*/
#ifndef KEY_H_
#define KEY_H_
#include "main.h"
#include "gpio.h"
GPIO_PinState get_key0_val();
GPIO_PinState get_key1_val();
GPIO_PinState get_key2_val();
uint8_t KEY_0(void);
uint8_t KEY_1(void);
uint8_t KEY_2(void);
#endif /* KEY_H_ */
key.c内容如下:
/*
* key.c
*
* Created on: Sep 29, 2022
* Author: py_hp
*/
#include "key.h"
GPIO_PinState get_key0_val()
{
return HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin);
};
GPIO_PinState get_key1_val()
{
return HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin);
};
GPIO_PinState get_key2_val()
{
return HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin);
};
uint8_t KEY_0(void)
{
uint8_t a;
a=0;//如果未进入按键处理,则返回0
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin)==GPIO_PIN_RESET){//读按键接口的电平
HAL_Delay(20);//延时去抖动
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin)==GPIO_PIN_RESET){ //读按键接口的电平
a=1;//进入按键处理,返回1
}
}
while(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin)==GPIO_PIN_RESET); //等待按键松开
return a;
}
uint8_t KEY_1(void)
{
uint8_t a;
a=0;//如果未进入按键处理,则返回0
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)==GPIO_PIN_RESET){//读按键接口的电平
HAL_Delay(20);//延时去抖动
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)==GPIO_PIN_RESET){ //读按键接口的电平
a=1;//进入按键处理,返回1
}
}
while(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)==GPIO_PIN_RESET); //等待按键松开
return a;
}
uint8_t KEY_2(void)
{
uint8_t a;
a=0;//如果未进入按键处理,则返回0
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin)==GPIO_PIN_RESET){//读按键接口的电平
HAL_Delay(20);//延时去抖动
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin)==GPIO_PIN_RESET){ //读按键接口的电平
a=1;//进入按键处理,返回1
}
}
while(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin)==GPIO_PIN_RESET); //等待按键松开
return a;
}
在main.c中调用串口驱动,其部分代码如下:
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
//用户代码1
#include "../../ICore/key.h"
#include "../../ICore/led.h"
#include "../../ICore/print.h"
#include "../../ICore/usart.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
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_LPUART1_UART_Init();
/* USER CODE BEGIN 2 */
//用户代码2
ResetPrintInit(&hlpuart1);
HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再开启接收中断
HLPUSART_RX_STA = 0;
set_led0_val(0);
set_led1_val(get_key0_val());
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
//用户代码3
if(KEY_1()) //按键KEY1判断为1时按键按下
{
set_led0_val(1);//LED1灯控制(1点亮,0熄灭)
set_led1_val(0);//LED2灯控制(1点亮,0熄灭)
printf("KEY1\r\n");//向USART1串口发送字符串
}
if(KEY_2()) //按键KEY2判断为1时按键按下
{
set_led0_val(0);//LED1灯控制(1点亮,0熄灭)
set_led1_val(1);//LED2灯控制(1点亮,0熄灭)
printf("KEY2\r\n");//向USART1串口发送字符串
}
if(HLPUSART_RX_STA&0xC000){//串口1判断中断接收标志位
printf("read flag HLPUSART_RX_STA&0XC000\r\n");//向USART1串口发送字符串
if(HLPUSART_RX_BUF[0]=='1'){
set_led0_val(1);//LED1灯控制(1点亮,0熄灭)
set_led1_val(1);//LED2灯控制(1点亮,0熄灭)
}
if(HLPUSART_RX_BUF[0]=='0'){
set_led0_val(0);//LED1灯控制(1点亮,0熄灭)
set_led1_val(0);//LED2灯控制(1点亮,0熄灭)
}
HLPUSART_RX_STA=0;//串口接收标志清0,即开启下一轮接收
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
四、编译测试
编译及加载程序到开发板:
验证数据收发,按键1和按键2按下,顺利传输数据,输入“1”和“0”字段,可下行控制LED灯