滴答时钟配置完了,现在调试手段还是只能通过单步断点,断点调试太麻烦也太不稳定。参考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
: 启禁用输入显示