printf复习

当我们写printf("%d\n", 1);的时候,printf函数并不能通过C语言语法得知第二个参数是int类型。printf是一个变参函数(variadic function):

int printf(const char *restrict format, ...);

参数的类型都是通过格式串format推导出的。如果参数类型与格式串中指定的不匹配,或提供的参数数量少于需要的,将导致未定义行为。

由于参数类型是动态的,printfscanf比静态类型的std::coutstd::cin慢,前提是后者的众多overhead被手动消除。

C为可变参数提供了va_startva_argva_copyva_endva_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的实例:

int vprintf(const char *format, va_list vlist);

可以借此实现自己的printf

可变参数在传递的过程中会被执行默认参数提升(default argument promotion),对于整数类型执行整数提升(提升为intunsigned int),对于float类型提升成double

格式串format中的普通字符直接拷贝到输出流,由%引导的称为转换格式(conversion specification),在%和转换说明符(conversion specifier)之间可以有若干修饰符,实现对齐、精度等功能,转换说明符有csdf等,详见cppreference

UART实现

单片机开发板并没有可以用于输出的控制台,printf调用最后都会归结为_write函数:

int _write(int file, char* ptr, int len);

_write函数需要把ptr指向的len字节的数据以想要的形式发送,在此就沿用上一篇中的UART异步IO,于是printf就可以打印在串口上了。

为了方便日后使用,我把USART相关的代码抽离出来放在一个新的源文件里,IDE生成的代码去掉MX_USART1_UART_InitUSART1_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);
}
}

ITM实现

明明已经用调试器连接了开发板和电脑,还要加个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个字节再返回吗?

STM32学习笔记——printf的更多相关文章

  1. stm32学习笔记----双串口同时打开时的printf()问题

    stm32学习笔记----双串口同时打开时的printf()问题 最近因为要使用串口2外接PN532芯片实现通信,另一方面,要使用串口1来将一些提示信息输出到上位机,于是重定义了printf(),使其 ...

  2. STM32学习笔记(四)——串口控制LED(中断方式)

    目录: 一.时钟使能,包括GPIO的时钟和串口的时钟使能 二.设置引脚复用映射 三.GPIO的初始化配置,注意要设置为复用模式 四.串口参数初始化配置 五.中断分组和中断优先级配置 六.设置串口中断类 ...

  3. STM32学习笔记——OLED屏

    STM32学习笔记--OLED屏 OLED屏的特点: 1.  模块有单色和双色可选,单色为纯蓝色,双色为黄蓝双色(本人选用双色): 2.  显示尺寸为0.96寸 3.  分辨率为128*64 4.   ...

  4. STM32学习笔记——点亮LED

    STM32学习笔记——点亮LED 本人学习STM32是直接通过操作stm32的寄存器,使用的开发板是野火ISO-V2版本: 先简单的介绍一下stm32的GPIO: stm32的GPIO有多种模式: 1 ...

  5. stm32学习笔记——外部中断的使用

    stm32学习笔记——外部中断的使用 基本概念 stm32中,每一个GPIO都可以触发一个外部中断,但是,GPIO的中断是以组为一个单位的,同组间的外部中断同一时间只能使用一个.比如说,PA0,PB0 ...

  6. STM32学习笔记-NVIC中断知识点

    STM32学习笔记-NVIC中断知识点总结 中断优先级设置步骤 1. 系统运行后先设置中断优先级分组 函数:void NVIC_PriorityGroupConfig(uint32_tNVIC_Pri ...

  7. STM32学习笔记之一(初窥STM32)

    怎么做好学习笔记? 答:自我感知-->学习知识-->归纳总结-->自我升华(真正属于自己的知识是,抛开书本,运用时,你还能记得的思想) 自我感知--看到知识概念,先自我感觉那应该是个 ...

  8. STM32学习笔记(二)——串口控制LED

    开发板芯片:STM32F407ZGT6 PA9-USART1_TX,PA10-USART1_RX; PF9-LED0,PF10-LED1; 一.串口1配置过程(不使用串口中断): 1.使能时钟,包括G ...

  9. STM32学习笔记(一)——点亮一个LED

    引言 最近报名了2017全国大学生电子设计竞赛,我们学校是第一次参加这个比赛,由于8/9月份就要比赛了,所以现在准备是比较晚的了,指导老师说只能做控制类的题目了,让我们学习一下STM32单片机,51到 ...

随机推荐

  1. 实现es6中的set和map

    转载自: https://www.cnblogs.com/hui-fly/p/9459152.html https://blog.csdn.net/roamingcode/article/detail ...

  2. 10.1Go Mysql

    第十章 Go Mysql 准备好mysql数据库服务端数据. 创建test数据库 MariaDB [(none)]> create database test; Query OK, 1 row ...

  3. Jenkins-Sonar集成配置及注意点

    首先说说关于Jenkins集成Sonar的相关配置:我jenkins与Sonar不在同一个服务器上! 先现在 SonarQube Scanner 插件. SonarQube Servers:系统配置 ...

  4. 存储系列之 硬盘接口与SCSI总线协议

    本文主要介绍硬盘的接口.总线和协议,SSD与SATA硬盘一般是兼容的,NVmeSSD除外. 一.磁盘控制器 上一章介绍了存储系统的主要介质硬盘,而硬盘的读写通过磁头臂,磁头臂是由磁盘驱动器来控制的.磁 ...

  5. vue2.0 axios前后端数据处理

    目前主流的 Vue 项目,都选择 axios 来完成 ajax 请求,而大型项目都会使用 Vuex 来管理数据. 前言: 使用 cnpm 安装 axios cnpm install axios -S ...

  6. [推荐]大量 Blazor 学习资源(一)

    前言 / Introduction Blazor 是什么? Blazor 允许您使用 C# 而不是 JavaScript 构建交互式 Web UI. Blazor 应用由使用 C#.HTML 和 CS ...

  7. 工作中遇到的SQL

    1.根据a表中的字段col,修改b表中的col UPDATE a INNER JOIN b ON a.id = b.id SET b.col = xx WHERE a.col = xx 2.模糊查询 ...

  8. Python中出现 SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 12-13: truncated \UXXXXXXXX escape

    Python中出现 SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 12-13: t ...

  9. Python 每日一练(4)

    引言 今天继续是python每日一练的几个专题,主要涵盖简单的敏感词识别以及图片爬虫 敏感词识别 这个敏感词的识别写的感觉比较简单,总的概括之后感觉功能可以简略成if filter_words in ...

  10. vscode jshint 报'import' is only available in ES6 (use 'esversion: 6'). (W119)错误

    vue项目用vscode打开代码前出现黄点,js报错 'import' is only available in ES6 (use 'esversion: 6'). (W119) 意思是import属 ...