USART发送数据

一、USART简介

USART即为通用同步异步收发器,用于串行通信,例如其可以用于打印程序输出信息,以便于调试程序。

USART框图

图10-1

这里简单介绍下USART框图。

TX为发送数据的输出引脚,RX为接收数据的输入引脚,SCLK为发送器时钟输出引脚(同步模式下会用到)。其中SCLK来源于APB1总线时钟(36MHz)和APB2总线时钟(72MHz)。

这里涉及到USART数据寄存器(USART_DR)。如图10-2。

图10-2

数据的发送和接收

从图10-2的寄存器描述我们知道,USART_DR实际上包含了一个发送用的TDR寄存器,一个接收用的RDR寄存器。发送时,把TDR内容转移到发送移位寄存器,由发送移位寄存器一位一位发出;接收时,把收到的每一位保存到接收移位寄存器然后再转移到RDR。

USART有专门的发送器和接收器,在使用USART前需要先使能USART,将USART_CR1寄存器的UE位置1即可。而发送或接收的数据字长可选8位或9位,由USART_CR1的M位控制。

要启动数据发送,需要先使能USART_CR1的TE位,则发送移位寄存器的数据会在TX引脚输出,从低位开始发送,如果是同步模式,则SCLK也会输出时钟信号。在异步模式中,一个字符帧包含三部分:起始位+数据帧+停止位。中间部分的数据帧则是我们要发送的8位或9位数据。当使能TE位后,发送器开始会先发送一个空闲帧,然后往USART_DR写入要发送的数据。发送完成后,等待状态寄存器(USART_SR)的TC位置1后,则代表数据传输完成,同时如果USART_CR1的TCIE位置1,将产生中断。

同理,在接收时,需要置位USART_CR1的RE位,使能接收。接收完成后,会把USART_SR的RXNE位置1,同时如果USART_CR1的RXNEIE位置1,可以产生中断。

USART_DR、USART_SR和USART_CR1~3需要结合使用,相关寄存器描述可自行查阅参考手册。

波特率相关

USART中,波特率和比特率的值相等,所以一般不区分这两个概念。波特率越大,传输速率越快。USART的发送器和接收器使用相同的波特率,公式如下:

boud =  f / (16*USARTDIV)

其中boud为波特率的值,f为USART时钟频率,USARTDIV是USART分频器除法因子,如图10-3的寄存器描述。

图10-3

 

由描述可知,DIV_Mantissa为USARTDIV的整数部分,DIV_Fraction为USARTDIV的小数部分。那么,

USARTDIV = DIV_Mantissa + DIV_Fraction / 16

波特率的常用值有2400、9600、19200、115200。

例如,挂载在APB2总线的USART1,其有72MHz的时钟频率,即f=72MHz,假设我们需要115200的波特率,则由上面的公式可得:

115200 = 72000000 / (16*USARTDIV)

我们能得到USARTDIV=39.0625,那么

DIV_Mantissa=39=0x17,
DIV_Fraction=0.625*16=1=0x01

这时我们应该设置USART_BRR的值为0x171。

校验控制

USART还支持奇偶校验。当使用校验位时,数据帧长度为8位数据帧加上1位校验位,共9位,此时USART_CR1的M位需要置1。将USART_CR1的PCE位置1可以使能校验控制。奇偶校验由硬件自动完成,在发送数据时会自动添加校验位,接收数据时会自动验证校验位。接收数据验证校验位时如果校验失败,USART_SR的PE位将会置1,同时如果USART_CR1的PEIE位置1,便能产生奇偶校验中断。

使能校验控制后,每个字符帧组成将变为:起始位+数据帧+校验位+停止位。

二、使用USART发送数据

  我们在写单片机程序的时候,在Debug时,往往要用到串口输出信息,这是会使用printf打印出我们想要的信息来,但是printf有一个弊端,就是输出打印时间较长。这样在一些对时间精度要求非常高的场合,使用printf将会带来一系列问题,这时,如果使用单片机的USART自定义一个协议,直接发送数据到上位机,将会得到我们想要的效果。下面对怎样使用USART发送数据做一个整理。

1、发送单个字符

void USART1_PutChar(USART_TypeDef * USARTx,u8 ch){
    USART_SendData8(USARTx,(u8)ch);
    while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE)==RESET);
    while(USART_GetFlagStatus(USARTx,USART_FLAG_TC) == RESET);
}

2、发送固定长度的字符串

void USART1_PutStrLen(USART_TypeDef * USARTx,u8 *buf,u16 len){
    for(;len > 0 ; len--) {
        USART_SendData8(USARTx,*buf++);    
        while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE) == RESET);
    }
    while(USART_GetFlagStatus(USARTx,USART_FLAG_TC) == RESET);
}

3、发送任意长度的字符串

void USART1_PutStr(USART_TypeDef * USARTx,u8 *buf){
    while(*buf){
        USART_SendData8(USARTx,*buf++);
        while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE) == RESET);
        // 这句话有必要加,他是用于检查串口是否发送完成的标志,如果不加这句话会发生数据丢失的情况。
    }
    while(USART_GetFlagStatus(USARTx,USART_FLAG_TC) == RESET);
    // 这句话有必要加,他是用于检查串口是否发送完成的标志,如果不加这句话会发生数据丢失的情况。
}

4、 实时操作系统(考虑函数重入)

该函数就可以像printf使用可变参数。通过观察函数但这个函数只支持了%d,%s的参数,想要支持更多,可以仿照printf的函数写法加以补充。

void USART_printf ( USART_TypeDef * USARTx, char * Data, ... ){
	const char *s;
	int d;   
	char buf[16];
	
	va_list ap;
	va_start(ap, Data);
 
	while ( * Data != 0 ){		  // 判断是否到达字符串结束符			                          
		if ( * Data == 0x5c ){	  //'\'								  
			switch ( *++Data ){
				case 'r':		//回车符
				    USART_SendData(USARTx, 0x0d);
				    Data ++;
				break;
 
				case 'n':	    //换行符
				    USART_SendData(USARTx, 0x0a);	
				    Data ++;
				break;
 
				default:
				    Data ++;
				break;
			}			 
		}		
		else if ( * Data == '%'){
			switch ( *++Data )
			{				
				case 's'://字符串
				    s = va_arg(ap, const char *);				
				    for ( ; *s; s++) {
					    USART_SendData(USARTx,*s);
					    while( USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET );
				    }				
				    Data++;				
				break; 
				case 'd'://十进制
				    d = va_arg(ap, int);				
				    itoa(d, buf, 10);				
				    for (s = buf; *s; s++){
					    USART_SendData(USARTx,*s);
					    while( USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET );
				    }				
				    Data++;				
				break;				
				default:
				    Data++;				
				break;				
			}		 
		}		
		else 
            USART_SendData(USARTx, *Data++);		
		while ( USART_GetFlagStatus ( USARTx, USART_FLAG_TXE ) == RESET );		
	}
}

二、使用USART接收数据

数据的头标识为“\n”既换行符,尾标识为“+”。该函数将串口接收的数据存放在USART_Buffer数组中,然后先判断当前字符是不是尾标识,如果是说明接收完毕,然后再来判断头标识是不是“+”号,如果还是那么就是我们想要的数据,接下来就可以进行相应数据的处理了。但如果不是那么就让Usart2_Rx=0重新接收数据。这样做的有以下好处:

  • 可以接受不定长度的数据,最大接收长度可以通过Max_BUFF_Len来更改
  • 可以接受指定的数据
  • 防止接收的数据使数组越界

        这里我的把接收正确数据直接打印出来,也可以通过设置标识位,然后在主函数里面轮询再操作。

1、正常接收

void USART2_IRQHandler(){
	if(USART_GetITStatus(USART2,USART_IT_RXNE) != RESET){ //中断产生 	
		USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除中断标志
			 
		Uart2_Buffer[Uart2_Rx] = USART_ReceiveData(USART2); //接收串口1数据到buff缓冲区
		Uart2_Rx++; 
     		 
		if(Uart2_Buffer[Uart2_Rx-1] == 0x0a || Uart2_Rx == Max_BUFF_Len){ //如果接收到尾标识是换行符(或者等于最大接受数就清空重新接收)		
			if(Uart2_Buffer[0] == '+'){ //检测到头标识是我们需要的 			
				printf("%s\r\n",Uart2_Buffer); //这里我做打印数据处理
				Uart2_Rx=0;                                   
			} 
			else{
				Uart2_Rx=0;//不是我们需要的数据或者达到最大接收数则开始重新接收
			}
		}
	}
}

2、DMA接收

串口空闲中断,一帧数据过来中断进入一次且接收的数据时候是DMA来搬运到指定缓冲区(程序中是USART1_RECEIVE_DMABuffer数组),不占用CPU时间。

#define DMA_USART1_RECEIVE_LEN 18        //根据需要设置
void USART1_IRQHandler(void){     
    u32 temp = 0;  
    uint16_t i = 0;  
      
    if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET){  
        USART1->SR;  
        USART1->DR; //这里我们通过先读SR(状态寄存器)和DR(数据寄存器)来清USART_IT_IDLE标志 			
        DMA_Cmd(DMA1_Channel5,DISABLE);  
        temp = DMA_USART1_RECEIVE_LEN - DMA_GetCurrDataCounter(DMA1_Channel5); //接收的字符串长度=设置的接收长度-剩余DMA缓存大小 
        for (i = 0;i < temp;i++){  
            Uart2_Buffer[i] = USART1_RECEIVE_DMABuffer[i];
        }  
        //设置传输数据长度  
        DMA_SetCurrDataCounter(DMA1_Channel5,DMA_USART1_RECEIVE_LEN);  
        //打开DMA  
        DMA_Cmd(DMA1_Channel5,ENABLE);  
    }        
}

 

 

 

 

 

 

 

 

 

 

 

  • 11
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

浮华蒲公英

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

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

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

打赏作者

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

抵扣说明:

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

余额充值