Stm32串口搭配DMA实现自定义printf、scanf

前言:本文仅供学习参考使用,主要目的是让大家快速使用串口调试,文章所提及的GCC适用于Clion,Vscode等第三方编辑器的用户。作者有时间会继续更新^_^

一、GCC环境

1、标准库

(1)、使用方法

在主函数while(1)初始化中,添加Serial_Init();

int main(void) {
    Serial_Init();

    while (1) {

    }
}

在代码目录下创建USART文件夹,新建syscalls.c,sysmem.c,usart.c,usart.h四个文件,工程目录结构如下:
在这里插入图片描述
在CMakeLists中添加如下代码:

# include_directories(Core/USART) 根据USART文件夹实际路径进行修改
set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -Wl,-u_printf_float")
add_link_options( -specs=nosys.specs -specs=nano.specs)

使用u_scanf,u_printf来替代scanf,printf函数,用法一致。

(2)代码部分

syscalls.c
/**
 ******************************************************************************
 * @file      syscalls.c
 * @author    Auto-generated by STM32CubeIDE
 * @brief     STM32CubeIDE Minimal System calls file
 *
 *            For more information about which c-functions
 *            need which of these lowlevel functions
 *            please consult the Newlib libc-manual
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2020-2024 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */

/* Includes */
#include <sys/stat.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <sys/times.h>


/* Variables */
extern int __io_putchar(int ch) __attribute__((weak));
extern int __io_getchar(void) __attribute__((weak));


char *__env[1] = { 0 };
char **environ = __env;


/* Functions */
void initialise_monitor_handles()
{
}

int _getpid(void)
{
  return 1;
}

int _kill(int pid, int sig)
{
  (void)pid;
  (void)sig;
  errno = EINVAL;
  return -1;
}

void _exit (int status)
{
  _kill(status, -1);
  while (1) {}    /* Make sure we hang here */
}

__attribute__((weak)) int _read(int file, char *ptr, int len)
{
  (void)file;
  int DataIdx;

  for (DataIdx = 0; DataIdx < len; DataIdx++)
  {
    *ptr++ = __io_getchar();
  }

  return len;
}

__attribute__((weak)) int _write(int file, char *ptr, int len)
{
  (void)file;
  int DataIdx;

  for (DataIdx = 0; DataIdx < len; DataIdx++)
  {
    __io_putchar(*ptr++);
  }
  return len;
}

int _close(int file)
{
  (void)file;
  return -1;
}


int _fstat(int file, struct stat *st)
{
  (void)file;
  st->st_mode = S_IFCHR;
  return 0;
}

int _isatty(int file)
{
  (void)file;
  return 1;
}

int _lseek(int file, int ptr, int dir)
{
  (void)file;
  (void)ptr;
  (void)dir;
  return 0;
}

int _open(char *path, int flags, ...)
{
  (void)path;
  (void)flags;
  /* Pretend like we always fail */
  return -1;
}

int _wait(int *status)
{
  (void)status;
  errno = ECHILD;
  return -1;
}

int _unlink(char *name)
{
  (void)name;
  errno = ENOENT;
  return -1;
}

int _times(struct tms *buf)
{
  (void)buf;
  return -1;
}

int _stat(char *file, struct stat *st)
{
  (void)file;
  st->st_mode = S_IFCHR;
  return 0;
}

int _link(char *old, char *new)
{
  (void)old;
  (void)new;
  errno = EMLINK;
  return -1;
}

int _fork(void)
{
  errno = EAGAIN;
  return -1;
}

int _execve(char *name, char **argv, char **env)
{
  (void)name;
  (void)argv;
  (void)env;
  errno = ENOMEM;
  return -1;
}

sysmem.c
/**
 ******************************************************************************
 * @file      sysmem.c
 * @author    Generated by STM32CubeIDE
 * @brief     STM32CubeIDE System Memory calls file
 *
 *            For more information about which C functions
 *            need which of these lowlevel functions
 *            please consult the newlib libc manual
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2024 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */

/* Includes */
#include <errno.h>
#include <stdint.h>

/**
 * Pointer to the current high watermark of the heap usage
 */
static uint8_t *__sbrk_heap_end = NULL;

/**
 * @brief _sbrk() allocates memory to the newlib heap and is used by malloc
 *        and others from the C library
 *
 * @verbatim
 * ############################################################################
 * #  .data  #  .bss  #       newlib heap       #          MSP stack          #
 * #         #        #                         # Reserved by _Min_Stack_Size #
 * ############################################################################
 * ^-- RAM start      ^-- _end                             _estack, RAM end --^
 * @endverbatim
 *
 * This implementation starts allocating at the '_end' linker symbol
 * The '_Min_Stack_Size' linker symbol reserves a memory for the MSP stack
 * The implementation considers '_estack' linker symbol to be RAM end
 * NOTE: If the MSP stack, at any point during execution, grows larger than the
 * reserved size, please increase the '_Min_Stack_Size'.
 *
 * @param incr Memory size
 * @return Pointer to allocated memory
 */
void *_sbrk(ptrdiff_t incr)
{
  extern uint8_t _end; /* Symbol defined in the linker script */
  extern uint8_t _estack; /* Symbol defined in the linker script */
  extern uint32_t _Min_Stack_Size; /* Symbol defined in the linker script */
  const uint32_t stack_limit = (uint32_t)&_estack - (uint32_t)&_Min_Stack_Size;
  const uint8_t *max_heap = (uint8_t *)stack_limit;
  uint8_t *prev_heap_end;

  /* Initialize heap end at first call */
  if (NULL == __sbrk_heap_end)
  {
    __sbrk_heap_end = &_end;
  }

  /* Protect heap from growing into the reserved MSP stack */
  if (__sbrk_heap_end + incr > max_heap)
  {
    errno = ENOMEM;
    return (void *)-1;
  }

  prev_heap_end = __sbrk_heap_end;
  __sbrk_heap_end += incr;

  return (void *)prev_heap_end;
}
usart.c

#include "usart.h"

uint8_t rx_buffer[BUFF_SIZE];
uint8_t tx_buffer[1];
volatile uint8_t Serial_RxFlag;

void GPIO_Init_Config(void) {
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

void USART_Init_Config(void) {
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    USART_InitTypeDef USART_InitStructure;
    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_HardwareFlowControl =
        USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_Init(USART1, &USART_InitStructure);
    USART_Cmd(USART1, ENABLE);
}

void DMA_Init_Config(DMA_Channel_TypeDef *DMA_Channel, uint32_t buffer,
                     uint32_t direction, uint32_t bufferSize) {
    DMA_InitTypeDef DMA_InitStructure;
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // Enable DMA1 clock
    DMA_DeInit(DMA_Channel);                           // Reset DMA channel
    DMA_InitStructure.DMA_PeripheralBaseAddr =
        (u32)(&USART1->DR);                        // Peripheral base address
    DMA_InitStructure.DMA_MemoryBaseAddr = buffer; // Memory base address
    DMA_InitStructure.DMA_DIR = direction;         // DMA transmit direction
    DMA_InitStructure.DMA_BufferSize = bufferSize; // DMA Channel buffer size
    DMA_InitStructure.DMA_PeripheralInc =
        DMA_PeripheralInc_Disable; // Peripheral address incremented
    DMA_InitStructure.DMA_MemoryInc =
        DMA_MemoryInc_Enable; // Memory address incremented
    DMA_InitStructure.DMA_PeripheralDataSize =
        DMA_PeripheralDataSize_Byte; // Peripheral data width
    DMA_InitStructure.DMA_MemoryDataSize =
        DMA_MemoryDataSize_Byte;                        // Memory data width
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;       // DMA Channel mode
    DMA_InitStructure.DMA_Priority = DMA_Priority_High; // DMA Channel priority
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // Memory-to-memory transfer
    DMA_Init(DMA_Channel, &DMA_InitStructure);   // DMA init
    DMA_Cmd(DMA_Channel, ENABLE);                // Enable DMA channel
}

void NVIC_Init_Config(void) {
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; // IRQ Channel
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =
        3;                                             // Preemption Priority
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // SubPriority Priority
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;    // Enable IRQ Channel
    NVIC_Init(&NVIC_InitStructure);                    // Init NVIC
}

void Serial_Init(void) {
    GPIO_Init_Config();
    USART_Init_Config();
    DMA_Init_Config(DMA1_Channel5, (u32)rx_buffer, DMA_DIR_PeripheralSRC,
                    BUFF_SIZE);
    DMA_Init_Config(DMA1_Channel4, (u32)tx_buffer, DMA_DIR_PeripheralDST, 1);
    NVIC_Init_Config();
    USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); // Enables Idle interrupt
    USART_GetFlagStatus(USART1, USART_FLAG_IDLE);  // Get Idle flag
    USART_ReceiveData(USART1);                     // Get receive data
    USART_Cmd(USART1, ENABLE);                     // Enable USART1
    USART_DMACmd(USART1, USART_DMAReq_Rx | USART_DMAReq_Tx,
                 ENABLE); // Enable DMA receive/transmit request
}

void Usart_SendByte(uint8_t ch) {
    tx_buffer[0] = ch;
    DMA_Cmd(DMA1_Channel4, DISABLE); //关闭 USART1 TX DMA1 所指示的通道
    DMA_SetCurrDataCounter(DMA1_Channel4, 1); //设置 DMA 缓存的大小
    DMA_Cmd(DMA1_Channel4, ENABLE); //使能 USART1 TX DMA1 所指示的通道
    //等待发送结束
    while (!DMA_GetFlagStatus(DMA1_FLAG_TC4))
        ;
    DMA_ClearFlag(DMA1_FLAG_TC4);
}
void USART_SendString(const char *str) {
    unsigned int i = 0;
    while (*(str + i) != '\0') {
        Usart_SendByte(*(str + i));
        i++;
    }
    while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
        ;
}

void USART1_IRQHandler(void) {
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        USART_ClearFlag(USART1, USART_FLAG_RXNE);
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    }

    if (USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) // Idle interrupt set
    {
        USART_GetITStatus(USART1, USART_IT_IDLE); // Get idle interrupt state
        USART_ReceiveData(USART1);                // Get USARTx received data
        DMA_Cmd(DMA1_Channel5, DISABLE);          // Disable DMA channel
        DMA_SetCurrDataCounter(DMA1_Channel5, BUFF_SIZE); // Set data count
        DMA_Cmd(DMA1_Channel5, ENABLE);                  // Enable DMA channel
        Serial_RxFlag = 1;
    }
}
char *find_next_space_or_end(char *str) {
    while (*str != ' ' && *str != '\0') {
        str++;
    }
    return str;
}

int str_to_num(char *start, char *end) {
    char temp[50];
    strncpy(temp, start, end - start);
    temp[end - start] = '\0';
    return atoi(temp);
}

double str_to_double(char *start, char *end) {
    char temp[50];
    strncpy(temp, start, end - start);
    temp[end - start] = '\0';
    return atof(temp);
}

void u_scanf(char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    char *start = rx_buffer;
    char *end;
    while (Serial_RxFlag == 0) {
    }
    Serial_RxFlag = 0;
    while (*fmt != '\0') {
        if (*fmt == '%') {
            fmt++;
            end = find_next_space_or_end(start);
            switch (*fmt) {
            case 'd': {
                int *ip = va_arg(ap, int *);
                *ip = str_to_num(start, end);
            } break;
            case 'l': {
                ++fmt;
                if (*fmt == 'f') {
                    double *fp = va_arg(ap, double *);
                    *fp = str_to_double(start, end);
                }
            } break;
            case 'f': {
                float *fp = va_arg(ap, float *);
                *fp = (float)str_to_double(start, end);
            } break;
            case 'c': {
                int *cp = va_arg(ap, int *);
                *cp = start[0];
            } break;
            case 's': {
                char *sp = va_arg(ap, char *);
                strcpy(sp, rx_buffer);
            } break;
            }
            start = end + 1;
        }
        fmt++;
    }
    va_end(ap);
}
void u_printf(const char *format, ...) {
    va_list args;
    uint8_t buff[BUFF_SIZE];
    va_start(args, format);
    vsprintf(buff, format, args);
    USART_SendString(buff);
    va_end(args);
}
usart.h
#ifndef __USART_H
#define __USART_H
#include "stdlib.h"
#include "stm32f10x.h"
#include "stm32f10x_dma.h"
#include "string.h"
#include <math.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/types.h>
#define BUFF_SIZE 256
void Serial_Init(void);
void u_printf(const char *format, ...);
void u_scanf(char *fmt, ...);
#endif

(3)总结

可以看出在代码中u_scanf比较繁琐,使用自定义函数来实现,并未使用自带的vsscanf来重构,如果你想要使用vsscanf的话,还需要添加-u_scanf_float链接标志(同上),但是vsscanf函数会导致输入字符串时无法正常读取空格.


以上版本存在问题,请替换usart.c,usart.h为如下代码,使用方法直接使用printf以及scanf,注意在CMakeLists中添加-u_scanf_float链接标志

usart.c

#include "usart.h"

/*根据需要取消注释对应宏定义*/
#define DBUG_1
//#define DBUG_2

#if defined(DBUG_1)
#define DBUG_USART USART1
#define DBUG_PORT GPIOA
#define RX_PIN GPIO_Pin_10
#define TX_PIN GPIO_Pin_9
#define RCC_APB2Periph_DBUG_PORT RCC_APB2Periph_GPIOA
#define DBUG_RX_CHANNEL DMA1_Channel5
#define DBUG_TX_CHANNEL DMA1_Channel4
#define DBUG_RX_FLAG DMA1_FLAG_TC5
#define DBUG_TX_FLAG DMA1_FLAG_TC4
#elif defined(DBUG_2)
#define DBUG_USART USART2
#define DBUG_PORT GPIOA
#define RX_PIN GPIO_Pin_3
#define TX_PIN GPIO_Pin_2
#define RCC_APB2Periph_DBUG_PORT RCC_APB2Periph_GPIOA
#define DBUG_RX_CHANNEL DMA1_Channel6
#define DBUG_TX_CHANNEL DMA1_Channel7
#define DBUG_RX_FLAG DMA1_FLAG_TC6
#define DBUG_TX_FLAG DMA1_FLAG_TC7
#endif

uint8_t rx_buffer[1];
uint8_t tx_buffer[1];
volatile uint8_t Serial_RxFlag;

void GPIO_Init_Config(void) {
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_DBUG_PORT, ENABLE);
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = TX_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(DBUG_PORT, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = RX_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(DBUG_PORT, &GPIO_InitStructure);
}

void USART_Init_Config(void) {
#if defined(DBUG_1)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
#elif defined(DBUG_2)
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
#endif
    USART_InitTypeDef USART_InitStructure;
    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_HardwareFlowControl =
        USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_Init(DBUG_USART, &USART_InitStructure);
    USART_Cmd(DBUG_USART, ENABLE);
}

void DMA_Init_Config(DMA_Channel_TypeDef *DMA_Channel, uint32_t buffer,
                     uint32_t direction, uint32_t bufferSize) {
    DMA_InitTypeDef DMA_InitStructure;
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    DMA_DeInit(DMA_Channel);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&DBUG_USART->DR);
    DMA_InitStructure.DMA_MemoryBaseAddr = buffer;
    DMA_InitStructure.DMA_DIR = direction;
    DMA_InitStructure.DMA_BufferSize = bufferSize;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA_Channel, &DMA_InitStructure);
    DMA_Cmd(DMA_Channel, ENABLE);
}

void Serial_Init(void) {
    GPIO_Init_Config();
    USART_Init_Config();
    DMA_Init_Config(DBUG_RX_CHANNEL, (u32)rx_buffer, DMA_DIR_PeripheralSRC, 1);
    DMA_Init_Config(DBUG_TX_CHANNEL, (u32)tx_buffer, DMA_DIR_PeripheralDST, 1);
    USART_Cmd(DBUG_USART, ENABLE);
    USART_DMACmd(DBUG_USART, USART_DMAReq_Rx | USART_DMAReq_Tx, ENABLE);
    setvbuf(stdin, NULL, _IONBF, 0);
}

#if (defined(__GNUC__) && !defined(__CC_ARM))
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#define GETCHAR_PROTOTYPE int __io_getchar(void)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#define GETCHAR_PROTOTYPE int fgetc(FILE *f)
#endif /* __GNUC__ */

PUTCHAR_PROTOTYPE {
    *tx_buffer = ch;
    while (DMA_GetCurrDataCounter(DBUG_TX_CHANNEL) != 0) {
    };
    DMA_Cmd(DBUG_TX_CHANNEL, DISABLE);
    DMA_SetCurrDataCounter(DBUG_TX_CHANNEL, 1);
    DMA_Cmd(DBUG_TX_CHANNEL, ENABLE);
    while (!DMA_GetFlagStatus(DBUG_TX_FLAG))
        ;
    DMA_ClearFlag(DBUG_TX_FLAG);
    return ch;
}
GETCHAR_PROTOTYPE {
    DMA_Cmd(DBUG_RX_CHANNEL, DISABLE);
    DMA_SetCurrDataCounter(DBUG_RX_CHANNEL, 1);
    DMA_Cmd(DBUG_RX_CHANNEL, ENABLE);
    while (!DMA_GetFlagStatus(DBUG_RX_FLAG))
        ;
    DMA_ClearFlag(DBUG_RX_FLAG);
    return *rx_buffer;
}
usart.h
#ifndef __USART_H
#define __USART_H
#include "delay.h"
#include "stdlib.h"
#include "stm32f10x.h"
#include "stm32f10x_dma.h"
#include "string.h"
#include <math.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/types.h>
void Serial_Init(void);

#endif
  • 11
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kk_阿白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值