1. 程式人生 > >STM32串列埠通訊中使用printf傳送資料配置方法(開發環境 Keil RVMDK)

STM32串列埠通訊中使用printf傳送資料配置方法(開發環境 Keil RVMDK)

出處:STM32串列埠通訊中使用printf傳送資料配置方法(開發環境 Keil RVMDK)

http://home.eeworld.com.cn/my/space-uid-338727-blogid-47176.html

在STM32串列埠通訊程式中使用printf傳送資料,非常的方便。可在剛開始使用的時候總是遇到問題,常見的是硬體訪真時無法進入main主函式,其實只要簡單的配置一下就可以了。

 

下面就說一下使用printf需要做哪些配置。

 

有兩種配置方法:

 

一、對工程屬性進行配置,詳細步驟如下

 

1、首先要在你的main 檔案中 包含“stdio.h” (標準輸入輸出標頭檔案)。

 

2、在main檔案中重定義<fputc>函式    如下:

 

// 傳送資料
   int fputc(int ch, FILE *f)
   {
      USART_SendData(USART1, (unsigned char) ch);// USART1 可以換成 USART2 等
      while (!(USART1->SR & USART_FLAG_TXE));
      return (ch);
   }
   // 接收資料
   int GetKey (void)  
  {
      while (!(USART1->SR & USART_FLAG_RXNE));
      return ((int)(USART1->DR & 0x1FF));
   }

 

這樣在使用printf時就會呼叫自定義的fputc函式,來發送字元。

 

3、在工程屬性的 “Target" -> "Code Generation" 選項中勾選 "Use MicroLIB"

   MicroLIB 是預設C的備份庫,關於它可以到網上查詢詳細資料。

 

二、第二種方法是在工程中新增“Regtarge.c”檔案

1、在main檔案中包含 “stdio.h” 檔案

2、在工程中建立一個檔案儲存為 Regtarge.c , 然後將其新增工程中在檔案中輸入如下內容(直接複製即可)

 

#include <stdio.h>
#include <rt_misc.h>
#pragma import(__use_no_semihosting_swi)
extern int  SendChar(int ch); // 宣告外部函式,在main檔案中定義
extern int  GetKey(void);
struct __FILE {
  int handle;                 // Add whatever you need here 
};
FILE __stdout;
FILE __stdin;
int fputc(int ch, FILE *f) {
  return (SendChar(ch));
}
int fgetc(FILE *f) {
  return (SendChar(GetKey()));
}
void _ttywrch(int ch) {
 SendChar (ch);
}
int ferror(FILE *f) {                            // Your implementation of ferror
  return EOF;
}
void _sys_exit(int return_code) {
label:  goto label;           // endless loop
}

 

3、在main檔案中新增定義以下兩個函式

 

int SendChar (int ch)  {
  while (!(USART1->SR & USART_FLAG_TXE)); // USART1 可換成你程式中通訊的串列埠
  USART1->DR = (ch & 0x1FF);
  return (ch);
}
int GetKey (void)  {
  while (!(USART1->SR & USART_FLAG_RXNE));
  return ((int)(USART1->DR & 0x1FF));
}

 

至此完成配置,可以在main檔案中隨意使用 printf 。

 

STM32程式新增printf函式後無法執行的解決方法(串列埠實驗) 

http://wojiushiwolxw.spaces.eepw.com.cn/articles/article/item/92847

標準庫函式的預設輸出裝置是顯示器,要實現在串列埠或LCD輸出,必須重定義標準庫函式裡呼叫的與輸出裝置相關的函式.

例如:printf輸出到串列埠,需要將fputc裡面的輸出指向串列埠(重定向),方法如下:

只要自己新增一個int fputc(int ch, FILE *f)函式,能夠輸出字元就可以了

 

#ifdef __GNUC__
/* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
PUTCHAR_PROTOTYPE
{
  /* Place your implementation of fputc here */
  /* e.g. write a character to the USART */
  USART_SendData(USART1, (uint8_t) ch);
  /* Loop until the end of transmission */
  while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
  return ch;
}

 


因printf()之類的函式,使用了半主機模式。使用標準庫會導致程式無法執行,以下是解決方法:

方法1.使用微庫,因為使用微庫的話,不會使用半主機模式.

方法2.仍然使用標準庫,在主程式新增下面程式碼:

 

#pragma import(__use_no_semihosting) 

_sys_exit(int x) 
{ 
  x = x; 
}
 
struct __FILE 
{ 
  int handle; 
}; 

FILE __stdout;

 

 

 

IAR EWARM

General Options -- Library Configuration -- Library : Full < file descriptor support >

 

#include <stdio.h>
#ifdef __GNUC__
/* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
PUTCHAR_PROTOTYPE
{
  /* Place your implementation of fputc here */
  /* e.g. write a character to the USART */
  USART_SendData(USART1, (uint8_t) ch);
  /* Loop until the end of transmission */
  while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
  return ch;
}

 

 

General Options -- Library Configuration -- Library : Normal < NO file descriptor support >

 

#include <stdio.h>

size_t __write(int handle, const unsigned char * buffer, size_t size)
{
  // byte by byte write
}

size_t __dwrite(int handle, const unsigned char * buffer, size_t size)
{
  // buffer[ 0x50 ]
}

 

 

Buffered Terminal Output : Enabled

xxwritebuffered.c

#define STORE_SIZE 80

static size_t storeLen = 0;
static unsigned char store[STORE_SIZE];

 

uint8_t store[ 0x50 ];

uint32_t storelen;

printf() --> __dwrite() : buffer[0x50]

 

Buffered Terminal Output : Disabled

printf() --> __write(), byte by byte

 

自定義輸出緩衝區

 

#define LOG_MAX_STR_LEN  512
void log_printf( const char * fmt, ... )
{
  char log_buf[ LOG_MAX_STR_LEN ];
  va_list args;

  va_start( args, fmt );
  int count = vsnprintf( log_buf, LOG_MAX_STR_LEN, fmt, args );
  va_end( args );

  // If an output error is encountered, a negative value is returned.
  if ( count < 0 )
    return;

  // "123456"    [123456][0X] : count = 6, n = 8
  // "1234567"   [1234567][0] : count = 7, n = 8
  // "12345678"  [1234567][0] : count = 8, n = 8
  // "123456789" [1234567][0] : count = 9, n = 8
  if ( count >= LOG_MAX_STR_LEN )
    count = LOG_MAX_STR_LEN - 1;

  // now log_buf is C string with the terminating null character
  __write(0, log_buf, count );
}

 

log_printf --> __write(), bufferred

stm32系列微控制器之printf重定向

http://leon0820.blog.51cto.com/5893766/1440146

在程式的除錯過程中,除了那些高大上的除錯手段外,printf無疑是我們最熟悉最順手的除錯方法。

通過使用printf,我們可以很方便很直觀的獲取當前程式的執行狀態。

printf()函式是格式化輸出函式, 一般用於向標準輸出裝置按規定格式輸出資訊。

但是在微控制器開發中,一般情況下並不存在標準輸出裝置,因此我們需要將printf的輸出資訊重定向,也就是輸出到其他輸出裝置中去。

在stm32平臺上實現重定向的方式有兩種,重定向至UART,或者通過JTAG的SW模式將printf重定向至SWO引腳輸出。

首先介紹第一種,重定向至UART,這種方式我們比較熟悉,ST官方提供的韌體庫中也是使用的這種方法。

程式碼如下:在對UART進行初始化後,通過如下程式碼對printf進行重定向

 

int fputc(int ch, FILE *f)
{
  USART_SendData(USART1, (uint8_t) ch);
  /* Loop until the end of transmission */
  while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
  {}
  return ch;
}

 

通過JTAG的SW模式將printf重定向至SWO引腳輸出

1.在原始碼中新增對ITM埠暫存器的定義

 

#define ITM_Port8(n)    (*((volatile unsigned char *)(0xE0000000+4*n)))
#define ITM_Port16(n)   (*((volatile unsigned short*)(0xE0000000+4*n)))
#define ITM_Port32(n)   (*((volatile unsigned long *)(0xE0000000+4*n)))

#define DEMCR           (*((volatile unsigned long *)(0xE000EDFC)))
#define TRCENA          0x01000000

 

2.通過如下程式碼將printf的輸出重定向至ITM的Port 0

 

int fputc(int ch, FILE *f) 
{
  if (DEMCR & TRCENA) {
    while (ITM_Port32(0) == 0);
    ITM_Port8(0) = ch;
  }
  return(ch);
}

 

3.通過printf輸出除錯資訊

printf("AD value = 0x%04X\r\n", AD_value);

4.將Jtag設定為SW模式,並設定ITM的Port 0 獲取資訊。

 

STM32中重定向printf到SWO口

http://www.dashashi.com/index.php/2014/03/1488

printf在命令列程式設計的時候是非常常用的,雖然是個老函式,但是功能強大,經久不衰

51等8位微控制器由於RAM比較小,棧就比較小,跑printf比較吃力,

但是STM32這種32位微控制器跑printf就很容易了,而作為一種除錯手段,printf十分方便、直觀。

比較常見的方法是把printf重定向到串列埠,不過這需要外接一個串列埠線,比較麻煩。

其實STM32自帶的SWO口是能夠非同步輸出資料的,而且不需要外接什麼裝置,

ST-LINK/J-Link等帶SWO口的偵錯程式都支援。

下面以STM32F4Discovery開發板+GCC為例說明。

根據這裡的方法,也可以把printf定位到其他外設。

PS:IAR在編譯選項裡自帶了printf via SWO的功能,就不需要外加設定了。

http://community.silabs.com/t5/Microcontroller-How-to-Guides/SWO-printf-in-IAR/td-p/98257

首先來說說怎麼把資訊輸出到SWO口,一句話搞定。

ITM_SendChar(ch);

這是在core_cm4.h(如果是F1系列的那就是core_cm3.h)中定義的行內函數。

不過不需要特意去include這個標頭檔案,通過#include "stm32f4xx.h"就間接地將core_cm4.h包含進來。

不過說起來,ITM這個東西其實嚴格來說是Cortex-M提供的一個特性,而不是STM32。

利用這個函式把資訊輸出到SWO口之後再開啟St-Link Utility,

在選單裡找到ST-LINK→Printf via SWO Viewer就會彈出一個視窗,

設定System Clock為微控制器核心頻率,點Start就能看到輸出的資訊了。

接下來就是把printf函式輸出的字串重定向過去了。

由於微控制器的外設功能是根據需求變化的,編譯器不可能確定printf需要用到的外設資源,

於是乎它就乾脆留了個介面,也就是_write函式,

當然除了_write函式之外還有_read等其他函式,不過這裡我們用不到。

其宣告為 int _write(int fd, char* ptr, int len);

關於_write函式,說簡單點,就是所有涉及到輸出字串的函式,

比如printf和putchar(),最終都會跑到_write函式,這裡fd是檔案識別符號,說開來就比較複雜了,

這裡我們用得到的就只有STDOUT_FILENO跟STDERR_FILENO,

其中前一個是標準輸出的檔案識別符號的預定義變數,後一個是錯誤輸出的檔案識別符號預定義變數。

第二個變數ptr是需要輸出的字串首地址,len就是輸出長度。

當我們呼叫printf函式後,C執行庫會把輸入變數轉換為最終需要輸出的字串,

然後呼叫_write函式,將結果輸出。我們的工作就是實現一個_write函式。

新建一個_write.c檔案,內容如下:

 

#include <stdio.h>
#include <unistd.h>

#include "stm32f10x.h"

#ifdef _DEBUG

int _write(int fd, char* ptr, int len)
{
if (fd == STDOUT_FILENO || fd == STDERR_FILENO)
{
int i = 0;
while(i<len)
ITM_SendChar(ptr[i++]);
}
return len;
}

#endif

 

 

 

加了個#ifdef _DEBUG 的效果是未加 _DEBUG 定義的時候就忽略下面的東西,

因為這東西主要是用在除錯階段,RELEASE版本里面都用不到了,而且多少還是會影響速度。

其他東西就很簡單了- -不需要多說明了吧。

直接編譯能通過,但是連結會報錯,提示無法找到_read之類的一堆函式。

在連結指令碼的下面libgcc.a ( * )後面加上libnosys.a ( * ),就不會報錯了。

具體原因涉及到Cortex-M3使用的newlib庫的實現,就不具體展開了。

好吧好吧,其實我也不知道。

如果想把資訊定位到串列埠,可以直接把ITM_SendChar改成相應的串列埠函式,

也可以利用DMA,先把資料拷貝到DMA緩衝區,讓DMA自動傳資料,提高響應速度。

STM32片內外設--DBG之Keil SWO輸出

http://blog.sina.com.cn/s/blog_79b01f6601018ymr.html

1) 加入stdio.h,這樣你就可以呼叫printf函數了

2) 使能SWO輸出

使能SWO輸出。最簡單的辦法就是將如下的函式拷貝到你的工程裡面,並且在mian函式初始化之後呼叫該函式。

 

void setupSWO(void)
{
uint32_t *dwt_ctrl = (uint32_t *) 0xE0001000;
uint32_t *tpiu_prescaler = (uint32_t *) 0xE0040010;
uint32_t *tpiu_protocol = (uint32_t *) 0xE00400F0;
 
CMU->HFPERCLKEN0 |= CMU_HFPERCLKEN0_GPIO;
 
GPIO->ROUTE |= GPIO_ROUTE_SWOPEN;
#if defined(_EFM32_GIANT_FAMILY)
 
GPIO->ROUTE = (GPIO->ROUTE & ~(_GPIO_ROUTE_SWLOCATION_MASK)) | GPIO_ROUTE_SWLOCATION_LOC0;
 
 
GPIO->P[5].MODEL &= ~(_GPIO_P_MODEL_MODE2_MASK);
GPIO->P[5].MODEL |= GPIO_P_MODEL_MODE2_PUSHPULL;
#else
 
GPIO->ROUTE = (GPIO->ROUTE & ~(_GPIO_ROUTE_SWLOCATION_MASK)) | GPIO_ROUTE_SWLOCATION_LOC1;
 
GPIO->P[2].MODEH &= ~(_GPIO_P_MODEH_MODE15_MASK);
GPIO->P[2].MODEH |= GPIO_P_MODEH_MODE15_PUSHPULL;
#endif
 
CMU->OSCENCMD = CMU_OSCENCMD_AUXHFRCOEN;
 
while(!(CMU->STATUS & CMU_STATUS_AUXHFRCORDY));
 
 
CoreDebug->DHCSR |= 1;
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
 
 
*dwt_ctrl = 0x400113FF;
 
*tpiu_prescaler = 0xf;
 
*tpiu_protocol = 2;
 
ITM->LAR = 0xC5ACCE55;
ITM->TCR = 0x10009;
}

 

3) 配置Keil的工程選項

開啟Keil的工程配置,選擇Debug頁面,選擇模擬器為Cortex-M/R J-Link/J-Trace, 並點選模擬器選項邊上的setting選項,開啟具體的設定視窗。

在開啟的視窗中,切換到Trace頁面,選中Enable,並且設定Core Clock為14MHz,分頻選項為Core Clock/16。詳情如下:

 

4) 在初始化SWO函式之後的地方,使用printf函式進行輸出。例如printf("Hello world")。

5) 在你的工程裡面,需要新增如下的函式:

 

struct __FILE 
{ 
  int handle; 
};

FILE __stdout;
FILE __stdin;

int fputc(int ch, FILE *f) 
{
  ITM_SendChar(ch);
  return(ch);
}

 

6) 編譯你的程式碼,並且進入Debug狀態

7) 開啟Keil的printf-view視窗, 通過 View -> Serial Windows -> Debug(printf) View

8) 點選執行之後,在Debug (printf) View裡即可檢視

Debug (printf) Viewer

http://www.keil.com/support/man/docs/jlink/jlink_trace_itm_viewer.htm

 

Segger RTT : Real Time Terminal

 http://segger.com/jlink-real-time-terminal.html