STM32 printf 方法重定向到串口UART


在嵌入式系統中調試代碼是很麻煩的一件事, 如果能方便地輸出調試信息(與調試者交互), 能使極大加快問題排查的過程. 串口在嵌入式領域是一個比較重要的通訊接口. 因為沒有顯示設備, 在單片機的程序里調用printf()打印內容是不可見的,但我們可以利用它的外設來實現printf(),比如串口, 串口基本上大多數單片機都有, 通常用串口來打印內容. 通過重寫fputc()函數來實現. fputc()是printf()的底層函數, 通過它把要打印的數據發送到串口上去.

不使用 MicroLib的普通方式

  1. 禁用半主機模式, 禁用了半主機模式才能使用標准庫函數printf()打印信息到串口
    說明: 半主機模式是ARM單片機的一種調試機制,跟串口調試不一樣,它需要通過仿真器來連接電腦,並調用相應的指令來實現單片機向電腦顯示器打印信息(或者從電腦鍵盤讀取輸入)。這種方法比串口調試更復雜, 需要用仿真器實現.
  2. include頭文件 #include "stdio.h"
  3. 重寫 fputc方法
  4. 重新定義 __FILE, __stdout, __stdin這三個變量, 以及重寫_sys_exit()
#pragma import(__use_no_semihosting_swi)

// Change it if you use different USART port
#define USARTx USART1

struct __FILE { int handle; };
FILE __stdout;
FILE __stdin;

int fputc(int ch, FILE *f) {
  while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET){}
  USART_SendData(USARTx, ch);
  return(ch);
}

int fgetc(FILE *f) {
  char ch;
  while(USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == RESET){}
  ch = USART_ReceiveData(USARTx);
  return((int)ch);
}

int ferror(FILE *f) {
  return EOF;
}

void _ttywrch(int ch) {
  while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){}
  USART_SendData(USARTx, ch);
}

void _sys_exit(int return_code) {
  while (1); /* endless loop */
}

使用 MicroLib (micro-library)

如果使用了keil uvsion開發環境, 可以用microlib簡化這一過程. MicroLib是一個定制(精簡)的stdio替代庫, 提供無緩沖的stdin, stdout 和 stderr,當使用微庫時,就默認關閉了半主機模式, 不需要#pragma注釋. 使用MicroLib之后, 只需要修改fputc()使其重定向

1. 在KEIL-MDK中開啟 Use MicroLIB 選項

打開配置面板, 定位到Target標簽頁, 勾選Use MicroLIB.

2. 將 fputc 方法的輸出重定向

在 MicroLib 的 stdio.h 頭文件中, fputc() 方法的prototype為

int fputc(int ch, FILE* stream)

這個方法原本是將ch輸出到strem這個文件類型指針指向的文件, 現在將其替換為串口1

#include <stdio.h>
int fputc(int ch, FILE* stream)
{
    USART_SendChar(USART1, (uint8_t)ch);
    return ch;
}

3. 重寫fgetc方法

同樣的

/*
** Rewrite fgetc function and make scanf function work
**/
int fgetc(FILE* file)
{
    while((USART1->ISR & UART_IT_RXNE) == RESET);
    return USART1->RDR;
}

注意要include stdio.h, 否則會報FILE類型未定義.

在代碼中啟用UART

根據可用的pin腳, USART1可以使用PA9, PA10組合, 或者PB6, PB7組合.

stm32f103

void UARTmain_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  USART_InitTypeDef USART_InitStructure;

  // 打開GPIO和USART時鍾
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
  
  // 將USART1 Tx@PA9的GPIO配置為推挽復用模式
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &GPIO_InitStructure);

  // 將USART1 Rx@PA10的GPIO配置為浮空輸入模式
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(GPIOA, &GPIO_InitStructure);

  /* 配置USART1參數
      波特率   = 115200
      數據長度 = 8
      停止位   = 1
      校驗位   = No
      禁止硬件流控(即禁止RTS和CTS)
      使能接收和發送
  */
  USART_InitStructure.USART_BaudRate = 115200;
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  USART_InitStructure.USART_StopBits = USART_StopBits_1;
  USART_InitStructure.USART_Parity = USART_Parity_No;
  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  USART_Init(USART1, &USART_InitStructure);

  // 使能 USART1
  USART_Cmd(USART1, ENABLE);
}

stm32f401

void UARTmain_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct;
  USART_InitTypeDef USART_InitStruct;
  
  /**
   * Enable clock for GPIOB
   * Enable clock for USART1 peripheral
   */
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

  /**
   * 串口1對應引腳復用映射
   * STM32F4xx USART1 為PA9/PB6對應USART1的TX, PA10/PB7對應USART1的RX
   * Tell pins PB6 and PB7 which alternating function you will use
   * @important Make sure, these lines are before pins configuration!
   */
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_USART1);
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_USART1);
  // Initialize pins as alternating function
  GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7;
  GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF;
  GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;
  GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_UP;
  GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
  GPIO_Init(GPIOB, &GPIO_InitStruct);

  /**
   * Set Baudrate to value you pass to function
   * Disable Hardware Flow control
   * Set Mode To TX and RX, so USART will work in full-duplex mode
   * Disable parity bit
   * Set 1 stop bit
   * Set Data bits to 8
   *
   * Initialize USART2
   * Activate USART2
   */  
  USART_InitStruct.USART_BaudRate=115200;
  USART_InitStruct.USART_WordLength=USART_WordLength_8b;
  USART_InitStruct.USART_StopBits=USART_StopBits_1;
  USART_InitStruct.USART_Parity=USART_Parity_No;
  USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
  USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;
  USART_Init(USART1, &USART_InitStruct);
  USART_Cmd(USART1, ENABLE);
}

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM