zoukankan      html  css  js  c++  java
  • STM32学习笔记——printf

    printf复习

    当我们写printf("%d ", 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
    ", 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
    ", 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函数即可发送,该函数定义在DriversCMSISIncludecore_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
    ", 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个字节再返回吗?

  • 相关阅读:
    linux 文件权限(s、t、i、a)解析
    vim Vundle
    数据结构学习(1)
    Android ImageView设置图片原理(上)
    C++11 之auto
    Android屏幕分辨率获取方法--源码剖析
    C++的发展方向是对的嘛?
    c++ 的前世今生
    学习知识的一种思路
    遗失的访谈小评
  • 原文地址:https://www.cnblogs.com/jerry-fuyi/p/13063308.html
Copyright © 2011-2022 走看看