Embedded development|simulating uart serial communication based on STM32

Remember to like it, please leave a message if you have any questions

Preface

  In embedded development, it is often used to print debugging information through the serial port. Sometimes in order to save costs, there is no extra serial port available. Therefore, the debugging information can be printed:
Method 1: Add SEGGER_RTT debugging print in the project
Method 2: Simulate serial port

UART working principle

  UART stands for Universal Asynchronous Receiver and Transmitter, which is a serial communication method. In the process of data transmission, communication is realized by transmitting one by one. The serial communication method has the advantages of fewer transmission lines and low cost. The disadvantage is that the speed is slow. Serial communication is divided into two types: synchronous communication and asynchronous communication. However, asynchronous communication methods are generally used, mainly because the receiving and sending clocks can be independent, which is beneficial to increase the flexibility of sending and receiving. Asynchronous communication is a character-by-character transmission. The information of a character consists of a start bit, a data bit, a parity bit, and a stop bit.
  The transmission of each character is synchronized by the start bit. The first bit of the character is the start bit, and the falling edge is used to notify the receiver to start the transmission. The data bit is immediately after the start bit, and the low bit is before the high bit during transmission. , The character itself is composed of 5 to 8 data bits. The data bit is followed by the parity bit, and finally the stop bit. The stop bit uses a high level to mark the end of a character and prepares for the transmission of the next character. The stop bit is followed by idle bits of different lengths. Both the stop bit and the idle bit are defined as high level, so that a falling edge of the start bit can be guaranteed. The frame format of

  UART is shown in the figure. The frame format of UART includes line idle state (idle, high level), start bit (start bit, low level), 5 to 8 data bits, parity bit, optional) and stop bit (stop bit, the number of bits can be 1, 1.5, 2).

UART simulation principle

  The analog mode of UART is basically realized by timer + IO port.

Option 1: Only print but not receive

  If in actual use only to print log without receiving data, you can use DWT plus ordinary IO port;

#define  VCOM_BOUND     115200
#define  VCOM_PIN       GPIO_Pin_11
#define  VCOM_PORT      GPIOA
#define  VCOM_PIN_HIGH  VCOM_PORT->BSRR = VCOM_PIN
#define  VCOM_PIN_LOW   VCOM_PORT->BRR  = VCOM_PIN

#define  BSP_REG_DEM_CR                           (*(volatile unsigned int *)0xE000EDFC)	//DEMCR寄存器
#define  BSP_REG_DWT_CR                           (*(volatile unsigned int *)0xE0001000)  	//DWT控制寄存器
#define  BSP_REG_DWT_CYCCNT                       (*(volatile unsigned int *)0xE0001004)	//DWT时钟计数寄存器	
#define  BSP_REG_DBGMCU_CR                        (*(volatile unsigned int *)0xE0042004)

#define  DEF_BIT_00                               0x01u
#define  DEF_BIT_24                               0x01000000u
#define  BSP_BIT_DEM_CR_TRCENA                    DEF_BIT_24			
#define  BSP_BIT_DWT_CR_CYCCNTENA                 DEF_BIT_00
static unsigned int  sys_clock = 48000000;

inline void dwt_start(void)
{
    
    
	BSP_REG_DEM_CR     |= (unsigned int)BSP_BIT_DEM_CR_TRCENA;
    BSP_REG_DWT_CYCCNT  = (unsigned int)0u;					       //初始化CYCCNT寄存器
    BSP_REG_DWT_CR     |= (unsigned int)BSP_BIT_DWT_CR_CYCCNTENA;    //开启CYCCNT	
}

inline void dwt_stop(void)
{
    
    
	BSP_REG_DWT_CR = 0;
}

void vcom_pin_init(void)
{
    
    	
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    GPIO_InitStructure.GPIO_Pin   = VCOM_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;
    GPIO_Init(VCOM_PORT, &GPIO_InitStructure);
    GPIO_SetBits(VCOM_PORT,VCOM_PIN);
	VCOM_PIN_HIGH;
}

void vcom_put_char(char ch)
{
    
    
	int i;
	int dat[8];
	uint32_t sys_clk, bit_width;
	volatile uint32_t time_stamp;
	
    sys_clk = sys_clock/1000000;  
    bit_width = 1000000*sys_clk/VCOM_BOUND;
    for(i=0; i<8; i++)           
    {
    
    
        if(ch & 0x01)
            dat[i] = 1;
        else
            dat[i] = 0;	
        ch >>= 1;
    }
    OS_CPU_SR cpu_sr;
    enter_critical();//以下代码进行临界保护,防止被中断打断造成发送误码
    dwt_start();
    VCOM_PIN_LOW; //发送起始位
	time_stamp = BSP_REG_DWT_CYCCNT;
	while(BSP_REG_DWT_CYCCNT < (time_stamp+bit_width))for(i=0; i<8; i++)
	{
    
    
		if(dat[i])
			VCOM_PIN_HIGH;
		else
			VCOM_PIN_LOW;
		time_stamp = BSP_REG_DWT_CYCCNT;
		while(BSP_REG_DWT_CYCCNT < (time_stamp+bit_width)); //发8bit 数据位
	}
	VCOM_PIN_HIGH;
	time_stamp = BSP_REG_DWT_CYCCNT;
	while(BSP_REG_DWT_CYCCNT < (time_stamp+bit_width));     //发停止位
	dwt_stop();
	exit_critical();
}

void vcom_printf(const char *fmt, ...)
{
    
    
    char buf[0x80];
    int  i;
    va_list ap;
	memset(buf, 0x00, sizeof(buf));
    va_start(ap, fmt);
    vsnprintf(buf, sizeof(buf), fmt, ap); 
    va_end(ap);	
	
	i = 0;
	while(buf[i])
	{
    
    
		vcom_put_char(buf[i]);
		i++;
	}
}

Option 2: Half-duplex UART

Implementation method: ordinary timer + ordinary IO port interrupt + fifo

/**
*软件串口的实现(IO模拟串口)
* 波特率:9600    1-8-N
* TXD : PC13
* RXD : PB14
* 使用外部中断对RXD的下降沿进行触发,使用定时器4按照9600波特率进行定时数据接收。
* Demo功能: 接收11个数据,然后把接收到的数据发送出去
*/


#define OI_TXD	PCout(13)
#define OI_RXD	PBin(14)

#define BuadRate_9600	100

u8 len = 0;	//接收计数
u8 USART_buf[11];  //接收缓冲区

enum{
    
    
	COM_START_BIT,
	COM_D0_BIT,
	COM_D1_BIT,
	COM_D2_BIT,
	COM_D3_BIT,
	COM_D4_BIT,
	COM_D5_BIT,
	COM_D6_BIT,
	COM_D7_BIT,
	COM_STOP_BIT,
};

u8 recvStat = COM_STOP_BIT;
u8 recvData = 0;

void IO_TXD(u8 Data)
{
    
    
	u8 i = 0;
	OI_TXD = 0;  
	delay_us(BuadRate_9600);
	for(i = 0; i < 8; i++)
	{
    
    
		if(Data&0x01)
			OI_TXD = 1;  
		else
			OI_TXD = 0; 	
		
		delay_us(BuadRate_9600);
		Data = Data>>1;
	}
	OI_TXD = 1;
	delay_us(BuadRate_9600);
}
	
void USART_Send(u8 *buf, u8 len)
{
    
    
	u8 t;
	for(t = 0; t < len; t++)
	{
    
    
		IO_TXD(buf[t]);
	}
}
	
 void IOConfig(void)
 {
    
    
	GPIO_InitTypeDef  GPIO_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	EXTI_InitTypeDef EXTI_InitStruct;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC, ENABLE);	 //使能PB,PC端口时钟 
	
	//SoftWare Serial TXD
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;	    
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz	 
	GPIO_Init(GPIOC, &GPIO_InitStructure);	  				
	GPIO_SetBits(GPIOC,GPIO_Pin_13); 						
	 
	 
	//SoftWare Serial RXD
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		
	GPIO_Init(GPIOB, &GPIO_InitStructure);	 
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
	EXTI_InitStruct.EXTI_Line = EXTI_Line14;
	EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;
	EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿触发中断
	EXTI_InitStruct.EXTI_LineCmd=ENABLE;
	EXTI_Init(&EXTI_InitStruct);

	NVIC_InitStructure.NVIC_IRQChannel= EXTI15_10_IRQn ; 
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; 
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =2;  
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;  
	NVIC_Init(&NVIC_InitStructure);  
	
}
 
void TIM4_Int_Init(u16 arr,u16 psc)
{
    
    
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //时钟使能
	
	//定时器TIM4初始化
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
	TIM_ClearITPendingBit(TIM4, TIM_FLAG_Update);
	TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
	
	//中断优先级NVIC设置
	NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;  //TIM4中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //先占优先级1级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  //从优先级1级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器			 
}
 
 
 int main(void)
 {
    
    		
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
	delay_init();
	IOConfig();
	TIM4_Int_Init(107, 71);	 //1M计数频率
	
	while(1)
	{
    
    
		if(len > 10)
		{
    
    
			len = 0;
			USART_Send(USART_buf,11);
		}
	}
}

void EXTI15_10_IRQHandler(void)
{
    
    
	if(EXTI_GetFlagStatus(EXTI_Line14) != RESET)
	{
    
    
		if(OI_RXD == 0) 
		{
    
    
			if(recvStat == COM_STOP_BIT)
			{
    
    
				recvStat = COM_START_BIT;
				TIM_Cmd(TIM4, ENABLE);
			}
		}
		EXTI_ClearITPendingBit(EXTI_Line14);
	}
}

void TIM4_IRQHandler(void)
{
    
      
	if(TIM_GetFlagStatus(TIM4, TIM_FLAG_Update) != RESET)
	{
    
    
		TIM_ClearITPendingBit(TIM4, TIM_FLAG_Update);	
		recvStat++;
		if(recvStat == COM_STOP_BIT)
		{
    
    
			TIM_Cmd(TIM4, DISABLE);
			USART_buf[len++] = recvData;	
			return;
		}
		if(OI_RXD)
		{
    
    
			recvData |= (1 << (recvStat - 1));
		}else{
    
    
			recvData &= ~(1 << (recvStat - 1));
		}	
	}		
}

Reference code: https://github.com/sckuck-bit/SoftWareSerial

Option 2: Full-duplex UART

Realization idea: adopt timer capture and compare, and require timer to correspond to IO


Code: Slightly

in conclusion

串口打印会占用CPU,调试完成记得关闭调试信息。

Guess you like

Origin blog.csdn.net/ziqi5543/article/details/115329735