當咱們寫printf("%d\n", 1);
的時候,printf
函數並不能經過C語言語法得知第二個參數是int
類型。printf
是一個變參函數(variadic function):html
int printf(const char *restrict format, ...);
參數的類型都是經過格式串format
推導出的。若是參數類型與格式串中指定的不匹配,或提供的參數數量少於須要的,將致使未定義行爲。異步
因爲參數類型是動態的,printf
和scanf
比靜態類型的std::cout
和std::cin
慢,前提是後者的衆多overhead被手動消除。函數
C爲可變參數提供了va_start
、va_arg
、va_copy
、va_end
、va_list
等工具,定義在頭文件<stdarg.h>
中。va_arg
用於取出參數,va_copy
用於拷貝參數供屢次使用。引用cppreference上的例子:工具
#include <stdio.h> #include <stdarg.h> #include <math.h> double sample_stddev(int count, ...) { /* Compute the mean with args1. */ double sum = 0; va_list args1; va_start(args1, count); va_list args2; va_copy(args2, args1); /* copy va_list object */ for (int i = 0; i < count; ++i) { double num = va_arg(args1, double); sum += num; } va_end(args1); double mean = sum / count; /* Compute standard deviation with args2 and mean. */ double sum_sq_diff = 0; for (int i = 0; i < count; ++i) { double num = va_arg(args2, double); sum_sq_diff += (num-mean) * (num-mean); } va_end(args2); return sqrt(sum_sq_diff / count); } int main(void) { printf("%f\n", sample_stddev(4, 25.0, 27.3, 26.9, 25.7)); }
<stdio.h>
還定義了vprintf
系列函數,與不帶v
的相比,可變參數...
都換成了va_list
的實例:ui
int vprintf(const char *format, va_list vlist);
能夠藉此實現本身的printf
。調試
可變參數在傳遞的過程當中會被執行默認參數提高(default argument promotion),對於整數類型執行整數提高(提高爲int
或unsigned int
),對於float
類型提高成double
。rest
格式串format
中的普通字符直接拷貝到輸出流,由%
引導的稱爲轉換格式(conversion specification),在%
和轉換說明符(conversion specifier)之間能夠有若干修飾符,實現對齊、精度等功能,轉換說明符有c
、s
、d
、f
等,詳見cppreference。code
單片機開發板並無能夠用於輸出的控制檯,printf
調用最後都會歸結爲_write
函數:orm
int _write(int file, char* ptr, int len);
_write
函數須要把ptr
指向的len
字節的數據以想要的形式發送,在此就沿用上一篇中的UART異步IO,因而printf
就能夠打印在串口上了。htm
爲了方便往後使用,我把USART相關的代碼抽離出來放在一個新的源文件裏,IDE生成的代碼去掉MX_USART1_UART_Init
和USART1_IRQHandler
兩個函數,再加上這一對文件就可使用了。
usart1.h
:
#include <stdio.h> void MX_USART1_UART_Init(); void usart1_transmit(char c); char usart1_receive();
usart1.c
:
#include "usart1.h" #include <stdbool.h> #include <stdint.h> #include <stdlib.h> #include "cmsis_gcc.h" #include "stm32f4xx_hal.h" typedef char queue_element_t; typedef struct { uint16_t mask; uint16_t head; uint16_t tail; queue_element_t data[0]; } queue_t; static inline queue_t* queue_create(uint16_t _size) { if (_size & (_size - 1)) _size = 256; queue_t* q = malloc(sizeof(queue_t) + _size * sizeof(queue_element_t)); if (q) { q->mask = _size - 1; q->head = q->tail = 0; } return q; } static inline bool queue_empty(const queue_t* _queue) { return _queue->head == _queue->tail; } static inline uint16_t queue_size(const queue_t* _queue) { return (_queue->tail - _queue->head) & _queue->mask; } static inline uint16_t queue_capacity(const queue_t* _queue) { return _queue->mask; } static inline queue_element_t queue_peek(const queue_t* _queue) { return _queue->data[_queue->head]; } static inline void queue_push(queue_t* _queue, const queue_element_t _ele) { _queue->data[_queue->tail] = _ele; _queue->tail = (_queue->tail + 1) & _queue->mask; } static inline void queue_pop(queue_t* _queue) { _queue->head = (_queue->head + 1) & _queue->mask; } extern UART_HandleTypeDef huart1; extern void Error_Handler(); queue_t* tx_buffer; queue_t* rx_buffer; void USART1_IRQHandler() { uint32_t isrflags = USART1->SR; uint32_t cr1its = USART1->CR1; uint32_t errorflags = 0x00U; errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE)); if (errorflags == RESET) { if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET)) { queue_push(rx_buffer, USART1->DR); return; } if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET)) { USART1->DR = queue_peek(tx_buffer); queue_pop(tx_buffer); if (queue_empty(tx_buffer)) USART1->CR1 &= ~USART_CR1_TXEIE & UART_IT_MASK; return; } } HAL_UART_IRQHandler(&huart1); } void MX_USART1_UART_Init() { tx_buffer = queue_create(1024); rx_buffer = queue_create(1024); huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } USART1->CR1 |= USART_CR1_RXNEIE & UART_IT_MASK; } void usart1_transmit(char c) { uint16_t capacity = queue_capacity(tx_buffer); bool ok = false; while (1) { __disable_irq(); ok = capacity - queue_size(tx_buffer) >= 1; if (ok) break; __enable_irq(); __NOP(); } queue_push(tx_buffer, c); USART1->CR1 |= USART_CR1_TXEIE & UART_IT_MASK; __enable_irq(); } char usart1_receive() { bool ok = false; while (1) { __disable_irq(); ok = !queue_empty(rx_buffer); if (ok) break; __enable_irq(); __NOP(); } char c = queue_peek(rx_buffer); queue_pop(rx_buffer); __enable_irq(); return c; } int _write(int file, char* ptr, int len) { for (int i = 0; i != len; ++i) usart1_transmit(*ptr++); return len; }
main.c
(部分):
#include "main.h" #include "usart1.h" UART_HandleTypeDef huart1; uint8_t count = 0; void SystemClock_Config(void); static void MX_GPIO_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); while (1) { printf("Hello world: %d\n", count); HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); ++count; HAL_Delay(500); } }
明明已經用調試器鏈接了開發板和電腦,還要加個USB轉串口工具就顯得很累贅;IDE和串口監視器兩個窗口的頻繁切換也讓Alt和Tab鍵損壞的概率增長了幾成。有沒有辦法讓開發板經過調試器和IDE就能輸出呢?
能夠用ARM的ITM(Instrumentation Trace Macroblock),經過TRACESWO
發送。SWO
與JTAG的JTDIO
是同一個引腳,用標準ST-LINK的20-pin排線能夠鏈接,可是10-pin的簡版ST-LINK沒有引出SWO
,所以要使用ITM調試不能用簡版的4線接法。
ITM無需初始化,直接調用ITM_SendChar
函數便可發送,該函數定義在\Drivers\CMSIS\Include\core_cmx.h
中。ITM版的_write
函數,不過是把usart1_transmit
換成ITM_SendChar
而已。
#include "main.h" #include <stdio.h> void SystemClock_Config(void); static void MX_GPIO_Init(void); int _write(int file, char* ptr, int len) { for (int i = 0; i != len; ++i) ITM_SendChar(*ptr++); return len; } uint8_t count = 0; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); while (1) { printf("Hello world: %d\n", count); HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); ++count; HAL_Delay(500); } }
爲了在IDE中看到printf
輸出的內容,須要作幾步配置。首先進入Debug模式,在調試選項的Debugger頁啓用SWV:
找到SWV ITM Data Console窗口:
窗口右上角Configure trace,勾選Port 0:
點擊Start Trace。這樣就能夠看見printf
的輸出了:
很久沒更博客了。這兩週一直在作搖搖棒,硬件軟件交替着改,總算是作出一個比較穩定的顯示效果了。計劃本月再更兩篇。
有一次下載器與搖搖棒的鏈接有鬆動,數據傳輸錯誤,致使熔絲位被修改,時鐘源選擇了不存在的,程序沒法啓動,也沒法下載新的程序。還好我帶着這塊STM32開發板,在一個引腳上產生一個較高頻率的方波,鏈接到單片機的晶振引腳,改回熔絲位,算是把單片機救活了。原本STM32開發板帶着是要寫這篇printf
的,博客沒寫,卻是有救場的用途。
與printf
相對的scanf
,我也嘗試過實現,可是有兩個問題,一是我沒有找到在STM32CubeIDE中如何經過ITM向單片機發送,二是_read
函數的len
參數老是1024
,這是想讓我一次性讀1024
個字節再返回嗎?