stm32学习之路——使用串口打印日志

滴答时钟配置完了,现在调试手段还是只能通过单步断点,断点调试太麻烦也太不稳定。参考linux内核的方式将日志加到串口上,通过串口输出一下日志。

串口的配置很简单,这里使用USART2,主要是因为引脚比较好接线。

在这里插入图片描述

使用同步模式,不配置中断。为了打印日志所以不想搞太复杂,异步或中断还要考虑实现buffer来处理缓冲区问题,可以但没必要,简单最好。

配置完就可以生成代码了,生成出来后,查看一下可以用哪些api

// Drivers/STM32F1xx_HAL_Driver/Inc/stm32f1xx_hal_usart.h
HAL_StatusTypeDef HAL_USART_Transmit(USART_HandleTypeDef *husart, const uint8_t *pTxData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_USART_Receive(USART_HandleTypeDef *husart, uint8_t *pRxData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_USART_TransmitReceive(USART_HandleTypeDef *husart, const uint8_t *pTxData, uint8_t *pRxData,
                                            uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_USART_Transmit_IT(USART_HandleTypeDef *husart, const uint8_t *pTxData, uint16_t Size);
HAL_StatusTypeDef HAL_USART_Receive_IT(USART_HandleTypeDef *husart, uint8_t *pRxData, uint16_t Size);
HAL_StatusTypeDef HAL_USART_TransmitReceive_IT(USART_HandleTypeDef *husart, const uint8_t *pTxData, uint8_t *pRxData,
                                               uint16_t Size);
HAL_StatusTypeDef HAL_USART_Transmit_DMA(USART_HandleTypeDef *husart, const uint8_t *pTxData, uint16_t Size);
HAL_StatusTypeDef HAL_USART_Receive_DMA(USART_HandleTypeDef *husart, uint8_t *pRxData, uint16_t Size);
HAL_StatusTypeDef HAL_USART_TransmitReceive_DMA(USART_HandleTypeDef *husart, const uint8_t *pTxData, uint8_t *pRxData,
                                                uint16_t Size);
HAL_StatusTypeDef HAL_USART_DMAPause(USART_HandleTypeDef *husart);
HAL_StatusTypeDef HAL_USART_DMAResume(USART_HandleTypeDef *husart);
HAL_StatusTypeDef HAL_USART_DMAStop(USART_HandleTypeDef *husart);
/* Transfer Abort functions */
HAL_StatusTypeDef HAL_USART_Abort(USART_HandleTypeDef *husart);
HAL_StatusTypeDef HAL_USART_Abort_IT(USART_HandleTypeDef *husart);

由于我们没开启中断,而且用来打日志,就只需要一个函数HAL_USART_Transmit。工程生成出来之后,可以看到main.c中有了对应代码。

// Core/Src/main.c
USART_HandleTypeDef husart2;
...
static void MX_USART2_Init(void);
...
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  ...
  MX_USART2_Init();
  ...
}

/**
  * @brief USART2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART2_Init(void)
{

  /* USER CODE BEGIN USART2_Init 0 */

  /* USER CODE END USART2_Init 0 */

  /* USER CODE BEGIN USART2_Init 1 */

  /* USER CODE END USART2_Init 1 */
  husart2.Instance = USART2;
  husart2.Init.BaudRate = 115200;
  husart2.Init.WordLength = USART_WORDLENGTH_8B;
  husart2.Init.StopBits = USART_STOPBITS_1;
  husart2.Init.Parity = USART_PARITY_NONE;
  husart2.Init.Mode = USART_MODE_TX_RX;
  husart2.Init.CLKPolarity = USART_POLARITY_LOW;
  husart2.Init.CLKPhase = USART_PHASE_1EDGE;
  husart2.Init.CLKLastBit = USART_LASTBIT_DISABLE;
  if (HAL_USART_Init(&husart2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART2_Init 2 */

  /* USER CODE END USART2_Init 2 */

}

可以看到,配置很简单。但是我们是要搞日志输出的,写一下日志输出函数,分为log.c和log.h

// log.h
#pragma once

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

/**
 * 以网络结构显示数据
 * @param data 数据首地址
 * @param length 数据长度
 */
static void log_hex_fn(const void *data, int length, void (*log_print)(const char *, ...)) {
    int i = 0, j = 0;
    const char *pData = (const char *)data;

    log_print("    ");
    for (i = 0; i < 16; i++) {
        log_print("%X  ", i);
    }
    log_print("    ");
    for (i = 0; i < 16; i++) {
        log_print("%X", i);
    }

    log_print("\r\n");

    for (i = 0; i < length; i += 16) {
        log_print("%02x  ", i / 16);
        for (j = i; j < i + 16 && j < length; j++) {
            log_print("%02x ", pData[j] & 0xff);
        }
        if (j == length && length % 16) {
            for (j = 0; j < (16 - length % 16); j++) {
                log_print("   ");
            }
        }
        log_print("    ");
        for (j = i; j < i + 16 && j < length; j++) {
            if (pData[j] < 32 || pData[j] >= 127) {
                log_print(".");
            } else {
                log_print("%c", pData[j] & 0xff);
            }
        }

        log_print("\r\n");
    }
}

void serial_printf(const char *fmt, ...);

void _print_current_time(void);
/**
 * 切分文件名,将前面的斜杠去除
 * @param fileName 文件名
 */
const char *_split_file_name(const char *fileName);

#define LOG_INFO(fmt, ...) \
    _print_current_time(); \
    serial_printf("[I][%s:%d %s] " fmt "\n", _split_file_name(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__)
#define LOG_WARN(fmt, ...) \
    _print_current_time(); \
    serial_printf("[W][%s:%d %s] " fmt "\n", _split_file_name(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...) \
    _print_current_time();  \
    serial_printf("[E][%s:%d %s] " fmt "\n", __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__)

#define LOG_HEX(data, len) log_hex_fn(data, len, serial_printf)

#ifdef __cplusplus
}
#endif  /* __cplusplus */

对应源文件如下

// log.c
#include <linux/jiffies.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

#include "stm32f1xx.h"

extern USART_HandleTypeDef husart2;
static inline int serial_putc(unsigned char ch) {
    unsigned timeout = 0xffff;
    HAL_USART_Transmit(&husart2, &ch, 1, timeout);
    return timeout ? 0 : -1;
}

static inline void serial_write(const char *s, unsigned n) {
    while (*s && n-- > 0) {
        if (*s == '\n') serial_putc('\r');
        serial_putc(*s);
        s++;
    }
}

void serial_printf(const char *fmt, ...) {
    va_list ap;
    char buf[512];
    int n;

    va_start(ap, fmt);
    n = vsnprintf(buf, sizeof(buf), fmt, ap);
    va_end(ap);

    serial_write(buf, n);
}

void _print_current_time(void) {
    unsigned long msecs = jiffies_to_msecs(jiffies);
    unsigned long secs = msecs / 1000;
    unsigned long msec = msecs % 1000;
    serial_printf("[%5lu.%03lu]", secs, msec);
}

const char *_split_file_name(const char *fileName) {
    const char *pChar = fileName;
    pChar = (strrchr(pChar, '/') ? strrchr(pChar, '/') + 1 : (strrchr(pChar, '\\') ? strrchr(pChar, '\\') + 1 : pChar));
    return pChar;
}

实现很简单,在主函数里面使用一下LOG_INFO("hello, main!")就可以从串口输出日志了

linux读取串口信息

我使用的是ch340的串口转USB的东西,CH340系列在linux上需要看看设备有没有驱动,没有需要下载

=> modinfo ch341
filename:       /lib/modules/6.11.6-arch1-1/kernel/drivers/usb/serial/ch341.ko.zst
...

下载地址在 https://www.wch.cn/download/CH341SER_LINUX_ZIP.html

驱动没问题后,可以从dmesg中看到usb插上后映射到/dev/ttyUSBx,就可以使用minicom来进行串口读写了,打开命令如下

# -D /dev/ttyUSB0   打开设备是/dev/ttyUSB0
# -b 115200         设置波特率为115200
# -w                启动自动换行
# -h                使用hex显示
minicom -D /dev/ttyUSB0 -b 115200 -w

minicom快捷键

  • Ctrl+a,x: 退出
  • Ctrl+a,z: 打开帮助面板
  • Ctrl+a,c: 清屏
  • Ctrl+a,w: 启禁用自动换行
  • Ctrl+a,e: 启禁用输入显示

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员kid1412

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值