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. [tgpl]go匿名函数

    [tgpl]go匿名函数 0. 定义 匿名函数顾名思义是没有名字的函数, Named functions can be declared only at the package level, but ...

  2. ThreadLocal 内存泄漏问题深入分析

    写在前面 ThreadLocal 基本用法本文就不介绍了,如果有不知道的小伙伴可以先了解一下,本文只研究 ThreadLocal 内存泄漏这一问题. ThreadLocal 会发生内存泄漏吗? 先给出 ...

  3. .NET 合并程序集(将 dll 合并到 exe 中)

    ------------恢复内容开始------------ ------------恢复内容开始------------ 背景:我们的应用程序通常都是由多个程序集组成,例如一个 exe 程序依赖于多 ...

  4. Flex打印功能 (2011-05-21 17:16:14)

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

  5. Java并发(3)

    线程安全: 允许被多个线程同时执行的代码称作线程安全的代码.线程安全的代码不包含竞态条件.当多个线程同时更新共享资源时会引发竞态条件.因此,了解Java线程执行时共享了什么资源很重要. 局部变量: 局 ...

  6. 当 RocketMQ 遇上 Serverless,会碰撞出怎样的火花?

    作者 | 元毅  阿里巴巴高级开发工程师 阿里巴巴云原生公众号后台回复 Knative,免费下载<Knative 云原生应用开发指南>电子书! 想必大家都比较了解 RocketMQ 消息服 ...

  7. ASP.NET Core on K8S学习之旅(14)Ingress灰度发布

    本篇已加入<.NET Core on K8S学习实践系列文章索引>,可以点击查看更多容器化技术相关系列文章. 之前一篇介绍了Ingress的基本概念和Nginx Ingress的基本配置和 ...

  8. MySQL知识-MySQL同版本多实例的配置

    MySQL多实例的配置 1. 创建需要目录 [root@db01 ~]# rm -rf /data/330{7..9}/data/*[root@db01 ~]# rm -rf /binlog/330{ ...

  9. Nginx 的过滤模块是干啥用的?

    上一篇文章我写了 Nginx 的 11 个阶段,很多人都说太长了.这是出于文章完整性的考虑的,11 个阶段嘛,一次性说完就完事了.今天这篇文章比较短,看完没问题. 过滤模块的位置 之前我们介绍了 Ng ...

  10. 如何在npm发布轮子

    我们在前端工程开发中通常使用npm这个包管理器来安装各种好用的轮子(当然也有用yarn的),不安分的码工就想,也发布一个试试,哪怕只是一个小时候滚的铁环而不是轮子. 首先,要在 npmjs官网注册自己 ...