Embedded Sharing Collection 152~Dry Goods

1. Print STM32 MCU log without serial port

    The most commonly used is to output the uart log through the serial port. This method is simple to implement, and most embedded chips have serial port functions. But such a simple function is sometimes not so easy to use, such as:

  • A new chip, how to print the log when there is no serial port driver

  • Some applications have relatively high timing requirements, what should I do if the serial output log takes too long? Such as USB enumeration.

  •  Some bugs will appear during normal operation, but when the serial port log is opened, what should I do?

  • Some packages do not have a serial port, or the serial port has been used for other purposes, how to output the log 

    These issues are discussed below.

1 Output log information to SRAM

    To be precise, the log is not output here, but the log can be seen in a way without using the serial port. In the chip development stage, you can connect to the emulator for debugging, and you can use the breakpoint method to debug, but if some operations cannot be interrupted, you cannot use breakpoint debugging.

    At this time, you can consider printing the log to the SRAM, and then view the log buffer in the SRAM through the emulator after the entire operation is completed, so as to realize the indirect log output.

    The test platform used in this article is STM32F407 discovery, based on the usb host experimental code, which is also common to other embedded platforms. First define a structure for printing log, as follows:

  Define a section of SRAM space as log buffer:

static u8 log_buffer[LOG_MAX_LEN];

    The log buffer is a ring buffer, and the log can be printed infinitely in a small buffer. The disadvantage is also obvious. If the log is not output in time, it will be overwritten by a new one. The size of the Buffer is allocated according to the size of the SRAM, here 1kB is used. In order to facilitate the output parameters, use the printf function to format the output, you need to do the following configuration (Keil):   and include the header file #include <stdio.h>, and implement the function fputc() in the code:

Write data to SRAM:

 In order to control the log printing format conveniently, add a custom printing function in the header file. 

    Call DEBUG() directly where the log needs to be printed. The final effect is as follows. You can see the printed log from the Memory window:  2 Output the log through SWO

    You can see the log by printing the log to SRAM, but when there is a large amount of data, it may be overwritten before you can check it. To solve this problem, you can use the SWO of St-link to output the log, so you don't have to worry about the log being overwritten. Check that the SWO of the f407 discovery in the schematic diagram is already connected, otherwise you need to connect by flying wires yourself:

  Add the SWO operation function set in the log structure:

typedef struct
{
    u8 (*init)(void* arg);
    u8 (*print)(u8 ch);
    u8 (*print_dma)(u8* buffer, u32 len);
}log_func;

typedef struct
{
volatile u8     type;
    u8*             buffer;
volatile u32    write_idx;
volatile u32    read_idx;
//SWO
    log_func*       swo_log_func;
}log_dev;

    SWO only needs the print operation function, which is implemented as follows
:

u8 swo_print_ch(u8 ch)
{
    ITM_SendChar(ch);
return 0;
}

    Using SWO to output the log is also output to the log buffer first, and then output when the system is idle, of course, it can also be output directly. Delayed log output will affect the real-time performance of the log, while direct output will affect the running of time-sensitive code, so the choice depends on the situation where the log needs to be output.

    Call the output_ch() function in the while loop to output the log when the system is idle.

/*output log buffer to I/O*/
void output_ch(void)
{   
    u8 ch;
    volatile u32 tmp_write,tmp_read;
    tmp_write = log_dev_ptr->write_idx;
    tmp_read = log_dev_ptr->read_idx;

if(tmp_write != tmp_read)
    {
        ch = log_dev_ptr->buffer[tmp_read++];
//swo
if(log_dev_ptr->swo_log_func)
            log_dev_ptr->swo_log_func->print(ch);
if(tmp_read >= LOG_MAX_LEN)
        {
            log_dev_ptr->read_idx = 0;
        }
else
        {
            log_dev_ptr->read_idx = tmp_read;
        }
    }
}

2.1 Output via IDE

    To use the SWO output function in the IDE, you need to do the following configuration (Keil): You can see the output log in the window: 

2.2 Output via STM32 ST-LINK Utility

    Using the STM32 ST-LINK Utility does not require special settings, directly open the Printf via SWO viewer under the ST-LINK menu, and then press start: 3 Output the log through the serial port

    All of the above are methods that cannot be used temporarily in the serial port log, or are only temporarily used. For long-term use, it is still necessary to output the log through the serial port. After all, most of the time it is impossible to connect to the emulator. To add a serial port output log, you only need to add the serial port operation function set:

typedef struct
{
volatile u8     type;
    u8*             buffer;
volatile u32    write_idx;
volatile u32    read_idx;
volatile u32    dma_read_idx;
//uart
    log_func*       uart_log_func;
//SWO
    log_func*       swo_log_func;
}log_dev;

​​​​​​​​ Implement the serial port driver function:

 

  Adding serial port output log is similar to the process of passing SWO, so no more description will be given here. The problem to be discussed below is that the rate of the serial port is low, and it takes a long time to output data, which seriously affects the operation of the system.

    Although the impact can be mitigated by printing to SRAM first and then delaying the output, if the system is frequently interrupted or time-consuming calculations are required, the log may be lost. To solve this problem is to solve the problem of CPU and output data to the serial port at the same time. Embedded engineers can immediately think that DMA is a good solution.

    Use DMA to transfer the log data to the serial port output without affecting the CPU operation, so that the problem of time-consuming output of the serial port log and affecting the system can be solved. The serial port and DMA initialization functions are as follows:​​​​​​​​

u8 uart_log_init(void* arg)
{
    DMA_InitTypeDef DMA_InitStructure;
    u32* bound = (u32*)arg;
//GPIO端口设置
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2时钟
//串口2对应引脚复用映射
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2);
//USART2端口配置
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   //速度50MHz
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
    GPIO_Init(GPIOA,&GPIO_InitStructure);
//USART2初始化设置
    USART_InitStructure.USART_BaudRate = *bound;//波特率设置
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
    USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
    USART_InitStructure.USART_Mode = USART_Mode_Tx; //收发模式
    USART_Init(USART2, &USART_InitStructure); //初始化串口1
#ifdef LOG_UART_DMA_EN  
    USART_DMACmd(USART2,USART_DMAReq_Tx,ENABLE);
#endif
    USART_Cmd(USART2, ENABLE);  //使能串口1 
    USART_ClearFlag(USART2, USART_FLAG_TC);
while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
#ifdef LOG_UART_DMA_EN
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
//Config DMA channel, uart2 TX usb DMA1 Stream6 Channel
    DMA_DeInit(DMA1_Stream6);
    DMA_InitStructure.DMA_Channel = DMA_Channel_4;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART2->DR);
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; 
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    DMA_Init(DMA1_Stream6, &DMA_InitStructure);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
#endif
return 0;
}

    The function of DMA output to the serial port is as follows: 

   Here, for convenience, the query DMA status register is directly used. If necessary, it can be modified to the DMA interrupt mode. Check the Datasheet to find the stream6 of serial port 2 using DMA1 channel4: whaosoft      aiot   http://143ai.com The serial port assistant on the PC side can see the log output: 

Use DMA to move the data in the log buffer to the serial port, and the CPU can handle other things at the same time. This method has the least impact on the system and outputs the log in a timely manner. It is the most used method in actual use. And not only the serial port can be used, but other interfaces that can be operated by DMA (such as SPI, USB) can use this method to print the log.

4 Use the IO port to simulate the serial port output log

    The last thing to discuss is how to output the log when there is no serial port in some packages, or when the serial port has been used for other purposes. At this time, you can find a free ordinary IO and simulate the UART protocol to output the log to the serial port tool of the host computer. The commonly used UART protocol is as follows:  As long as the high and low levels are output on the IO at a certain time, the waveform can be simulated. This certain time is the serial port baud rate. In order to get accurate delay, here use TIM4 timer to generate 1us delay. Note: The timer cannot be reused. In the test project, TIM2 and TIM3 are both used. If it is reused, it will be messed up. The initialization function is as follows:​​​​​​​​

u8 simu_log_init(void* arg)
{
    TIM_TimeBaseInitTypeDef TIM_InitStructure;  
    u32* bound = (u32*)arg;
//GPIO端口设置
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   //速度50MHz
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
    GPIO_Init(GPIOA,&GPIO_InitStructure);
    GPIO_SetBits(GPIOA, GPIO_Pin_2);
//Config TIM
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE); //使能TIM4时钟
    TIM_DeInit(TIM4);
    TIM_InitStructure.TIM_Prescaler = 1;        //2分频
    TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_InitStructure.TIM_Period = 41;          //1us timer
    TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInit(TIM4, &TIM_InitStructure);
    TIM_ClearFlag(TIM4, TIM_FLAG_Update);
    baud_delay = 1000000/(*bound);          //根据波特率计算每个bit延时
return 0;
}

 The delay function using the timer is:

 The last is the analog output function. Note: the interrupt must be turned off before the output, and then turned on after a byte is output, otherwise there will be garbled characters:​​​​​​​​

u8 simu_print_ch(u8 ch)
{
volatile u8 i=8;
    __asm("cpsid i");
//start bit
    GPIO_ResetBits(GPIOA, GPIO_Pin_2);
    simu_delay(baud_delay);
while(i--)
    {
if(ch & 0x01)
        GPIO_SetBits(GPIOA, GPIO_Pin_2);
else
        GPIO_ResetBits(GPIOA, GPIO_Pin_2);
        ch >>= 1;
        simu_delay(baud_delay);
    }
//stop bit
    GPIO_SetBits(GPIOA, GPIO_Pin_2);
    simu_delay(baud_delay);
    simu_delay(baud_delay);
    __asm("cpsie i");
return 0;
}

    Using IO simulation can achieve a similar effect to the real serial port, and only needs one ordinary IO, which is relatively used on a small package chip.

 

Guess you like

Origin blog.csdn.net/qq_29788741/article/details/132008191