stm32f103读取电子罗盘数据

    前几天接到了一个任务,用stm32f103的串口读取电子罗盘(北微姿态传感器)的数据,其实也就三个,PITCH,ROLL,HEADING三个方向上的角度,顺带复习了一下串口知识。
    个人觉得熟悉一下串口协议还是很重要的,刚开始学习的时候跟着原子的视频学习还是学习的很认真的,奈何但是是为了比赛速成,所以看完视频就直接用原子的例程了,久而久之不太清楚串口通信的具体实现过程了,就知道怎么使用原子的串口例程。
    下面我们先来回顾一下串口的通信例程(已经会的大佬请略过)
    其实串口说到底就是一个通信工具,也就是说就是两个模块之间传输数据用的工具,那么传输数据,最重要的两个方面就是发送数据和接收数据这两个方面了,所以,我们今天也就着重从这两个方面来讲讲串口通信。
    在开始之前,我觉得有个串口通信的框图我们有必要先了解一下,不要求完全看懂,但至少需要了解一些。

 

    其实最重要的就是两(仅对于发送而言,接收同理的也是两个)

一、USARTx->SR(状态寄存器)

就如图上所说的,控制寄存器主要就是一些使能位以及一些中断标志位,咱们可以看看参考手册,用手册说话

 

显然,这个寄存器中很多都是一些中断标志位。

二、USARTX->DR(数据寄存器)

可以看出,数据寄存器只有八位有效位,所以咱们串口的数据是一字节一字节(8位)传输的。

关于数据寄存器,还有一个要注意的地方大家一定要注意。大家看数据手册。

也就是说,串口发送与接收的寄存器其实是两个寄存器,一个是TDR寄存器,一个是RDR寄存器(其实我感觉就是一个寄存器,只不过不同时候用处不一样而已,大家可以在MDk中找到USART_TypeDef这个结构体去看看定义,所以这个知识点我觉得有可能是我没有理解手册的意思),但是很爽的是我们在单片机里头的操作一概都是USARTx->DR=0x某某;

下面咱们来看看具体的收发过程

发送

显然,按照参考手册上说的,只需要向USARTx->DR中写数据数据就会自动发送,发送完了,SR寄存器的相应的标志就会置位。我们也可以设置相应的中断。这是单字节发送,在这个基础上,我们也可以非常轻松地实现多字节发送。下面是多字节发送程序。

  1. /************************************************************
    函数功能:连续发送数据
    函数参数  u8 *data  发送数据数组首元素地址
              times  发送数据字节数
    ·µ»ØÖµ £º  void
    ************************************************************/
    void constant_send(u8 *data,u8 times)
    {
    	int i;
    	for(i=0;i<times;i++)
    	{ 				 
             while((USART2->SR&0X80)==0);
    		 USART2->DR=*(data++);
    		     
    	}
    }

接收

咱们继续看数据手册,,,这东西确实是个好东西,有耐心一定可以学到很多东西的

从上面可以看出来,我们接收数据也是直接读取DR寄存器就行,我们读取串口接收到的数据一般使用串口接收中断(USART_IT_RXNE),取数据的时候,中断标志位就自动清除了,所以在中断处理函数中我们不用调用清除中断标志位的函数。

接收中断还有个中断我觉得很有必要了解一下,在接受不定长数组中很有作用——USART_IT_IDLE(帧中断),帧中断是检测传输过程中的数据断流,当一组数据传输完成之后,才会进入中断。这样,我们就能很方便接收不定长数据了。下面附上代码

我的协议的代码。

void uart_init(u32 bound){
  //GPIO端口设置
  GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef  NVIC_InitStructure;
	
	 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //使能usart2的时钟
	//USART1//2_TX  
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_2; //PA.9
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
   
  //USART1/2_RX	  
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_3;//PA10
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  

  //Usart1/2 NVIC 配置
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;  
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器

   //USART 初始化设置
	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_Rx | USART_Mode_Tx;	//收发模式
//使能初始化以及中断设置usart1
  USART_Init(USART1, &USART_InitStructure); //初始化串口1
  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断 
  USART_Cmd(USART1, ENABLE);                    //使能串口1 
	
	
  NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;  
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器

   //USART 初始化设置
	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_Rx | USART_Mode_Tx;	//收发模式	
//使能初始化以及中断设置usart2
  USART_Init(USART2, &USART_InitStructure); //初始化串口2
  USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启串口接受中断
	USART_ITConfig(USART2,USART_IT_IDLE,ENABLE);  //使能串口帧中断  帧中断寄存器的清除,,先读SR寄存器,在读DR寄存器
  USART_Cmd(USART2, ENABLE);                    //使能串口2
}

void USART1_IRQHandler(void)                	//串口1中断服务程序
	{
	u8 Res;
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
		{
	   printf("ha\n");		
		 Res =USART_ReceiveData(USART1);	//读取接收到的数据
		

			if((USART1_RX_STA&0x8000)==0)//接收未完成
			{
			if(USART1_RX_STA&0x4000)//接收到了0x0d
				{
				if(Res!=0x0a)USART1_RX_STA=0;//接收错误,重新开始
				else USART1_RX_STA|=0x8000;	//接收完成了 
				}
			else //还没收到0X0D
				{	
				if(Res==0x0d)USART1_RX_STA|=0x4000;
				else
					{
					USART1_RX_BUF[USART1_RX_STA&0X3FFF]=Res ;
					USART1_RX_STA++;
					if(USART1_RX_STA>(USART_REC_LEN-1))USART1_RX_STA=0;//接收数据错误,重新开始接收	  
					}		 
				}
			}   		 
     } 
   } 
/***************************************************************************************************************
函数功能:串口2接收中断函数	
入口参数:void
返回值:void
***************************************************************************************************************/
void USART2_IRQHandler(void)                	//串口1中断服务程序
 {
	 u8 clear=clear;
	if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)       //等待接收中断
		{
     USART2_RX_BUF[recount++]=USART_ReceiveData(USART2);
		}
	if(USART_GetITStatus(USART2,USART_IT_IDLE)!=RESET)
	{
		clear=USART2->SR;
		clear=USART2->DR;
		ReceiveState=1;
	}
} 
#endif
/************************************************************
函数功能:连续发送数据
函数参数:*data   u8类型指针,代表要发送的数据数组第一个数的地址
          times  循环次数
返回值 :  void
************************************************************/
void constant_send(u8 *data,u8 times)
{
	int i;
	for(i=0;i<times;i++)
	{ 				 
         while((USART2->SR&0X80)==0);
				 USART2->DR=*(data++);
		     
	}
}
///以下为获取传感器数据相关函数/
/**************************************************************
函数功能:获取PITCH角度值
入口参数:void
返回值:  void
描述:发送 77 04 00 01 05(16进制),传感器返籶pitch叵喙厥荩菸辉诘谒奈辶纸冢咛寮蔽⑼ㄐ判?
**************************************************************/
void get_pitch(void)
{
	 u16 data=data;              //这样子的作用可以排除没有使用的警告
	 command_string[0]=0x77;
	 command_string[1]=0x04;
   command_string[2]=0x00;
	 command_string[3]=0x01;
   command_string[4]=0x05;
   constant_send(command_string,5); //循环发送,直到发送完毕   
   while(1)
	 {
		 if(ReceiveState==1)
		 {
			 data=(USART2_RX_BUF[4]&0x0f)*10000;    //百位,这儿其实都是乘了100倍换成整数提高代码的效率,同时可以防止一次一次转化成浮点数带来的精度损失的问题
       data=data+(USART2_RX_BUF[5]&0xf0>>4)*1000+(USART2_RX_BUF[5]&0x0f)*100;  		   
			 data=data+(USART2_RX_BUF[6]&0xf0>>4)*10+(USART2_RX_BUF[6]&0x0f);
		   direction_angles[0]=(data*1.0)/100;
			if((USART2_RX_BUF[4]&0xf0)==0x10)
					     direction_angles[0]=-direction_angles[0];
	     ReceiveState=0;
       recount=0;		
       break;			 
		 }
	 }
}

关于串口收发,手册上还提供了一个更好的方法,用DMA通道(前提是支持DMA)直接进行数据传输。用DMA好处是为CPU减负,在要在开始和结束的阶段CPU参与一下就行,中间的数据传输阶段完全不用CPU参与,此时CPU可以去做其他事情。

关于串口DMA收发,我就写一下收发的要点

发送

1、初始化DMA通道(大家可以去参考原子的教程),使能串口DMA通道(这个容易忘记)

2、打开对应DMA的传输完成中断

3、在DMA的传输模式中,我们选择的是单次模式而不是循环模式,那么,我们如何重新开始发送数据呢,大家看手册。

手册上面说的很明白,我们先失能DMA对应的通道(只有先失能才能更改相应设置),然后重新写入需要传输的字节数,然后打开通道,对于接收而言也是同样的。

接收

1、初始化DMA通道(大家可以去参考原子的教程),使能串口DMA通道(这个容易忘记)

2、对于接收不定长数据,我们不能使用DMA通道的传输完成中断,因为数组的不定长,有可能会使DMA不能产生传输完成中断,这个时候,我们可以使用定时器超时中断,不过建议使用串口接收帧中断。

3、如何重新初始化接收和初始化发送是一样的

下面附上代码

void usart2_Init(u32 bound)
{
  GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//使能GPIO0A时钟
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //使能串口2时钟
	//USART1_TX   GPIOA.9
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA.2
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.2
   
  //USART1_RX	  GPIOA.10初始化
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//PA3
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.3 

  //Usart1 NVIC 配置
  NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
  
   //USART 初始化设置

	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_Rx | USART_Mode_Tx;	//收发模式
  USART_Init(USART2, &USART_InitStructure); //初始化串口1
  USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);//开启串口接受帧中断
	USART_DMACmd(USART2,USART_DMAReq_Tx,ENABLE);
	USART_DMACmd(USART2,USART_DMAReq_Rx,ENABLE);
  USART_Cmd(USART2, ENABLE);                    //使能串口1 
}
/**************************************************
函数功能:串口2的DMA初始化
函数参数:void
函数返回值:void
**************************************************/
void usart2_dma(void)
{
	DMA_InitTypeDef  usart2dma;
	NVIC_InitTypeDef  DMA_NVIC;	    
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);   //使能DMA1的时钟
	//中断配置	
	DMA_NVIC.NVIC_IRQChannel=DMA1_Channel7_IRQn;           //DMA1发送完成中断
	DMA_NVIC.NVIC_IRQChannelPreemptionPriority=1;          //抢占优先级为最高优先级
	DMA_NVIC.NVIC_IRQChannelSubPriority=0;
	DMA_NVIC.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&DMA_NVIC);	//根据指定的参数初始化VIC寄存器
//设置USART-DMA的接收	
	DMA_DeInit(DMA1_Channel6);                            //USART2的接收DMA设置成缺省位     
  usart2dma.DMA_PeripheralBaseAddr=(u32)(USART2->DR);    //外设基地址 //在指针面前加一个(u32),数据类型的强制转化,把指针类型的数据转化成u32
  usart2dma.DMA_MemoryBaseAddr=(u32)USART2_RX_BUF;      //内存基地址
	usart2dma.DMA_DIR=DMA_DIR_PeripheralSRC;              //外设到内存
	usart2dma.DMA_BufferSize=20;                          //一次传输10字节
	usart2dma.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//外设地址不变,一直为USART2;
	usart2dma.DMA_MemoryInc=DMA_MemoryInc_Enable;         //内存地址增加
  usart2dma.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;  //外设字节传输
  usart2dma.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;      //内存字节传输
	usart2dma.DMA_Mode=DMA_Mode_Normal;                        //不采用循环采集的模式,采集一次便可,需要的花继续采集防止出现错误
	usart2dma.DMA_Priority=DMA_Priority_High;
	usart2dma.DMA_M2M=DMA_M2M_Disable;	
	DMA_ClearFlag(DMA1_FLAG_GL6);                          //清除所有的标志位
	DMA_Init(DMA1_Channel6,&usart2dma);                    //串口2的接收对应着DMA1的channel6
	DMA_Cmd(DMA1_Channel6, ENABLE);  //使能USART1 TX DMA1 所指示的通道 
//设置usart_dma的发送
	DMA_DeInit(DMA1_Channel7);  
	usart2dma.DMA_PeripheralBaseAddr=(u32)&(USART2->DR);    //外设基地址 
	usart2dma.DMA_MemoryBaseAddr=(u32)USART2_TX_BUF;      //内存基地址
	usart2dma.DMA_DIR=DMA_DIR_PeripheralDST;              //内存外设
	usart2dma.DMA_BufferSize=0;                         //一次传输0字节,需要的时候更改就行
	usart2dma.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//外设地址不变,一直为USART2;
	usart2dma.DMA_MemoryInc=DMA_MemoryInc_Enable;         //内存地址增加
  usart2dma.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;  //外设字节传输
  usart2dma.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;      //内存字节传输
	usart2dma.DMA_Mode=DMA_Mode_Normal;                        //不采用循环采集的模式,采集一次便可,需要的花继续采集防止出现错误
	usart2dma.DMA_Priority=DMA_Priority_VeryHigh;
	usart2dma.DMA_M2M=DMA_M2M_Disable;
	DMA_ClearFlag(DMA1_FLAG_GL7);                          //清除所有的标志位
	DMA_Init(DMA1_Channel7,&usart2dma);                    //串口2的接收对应着DMA1的channel6
  DMA_ITConfig(DMA1_Channel7,DMA_IT_TC,ENABLE);          //DMA传输完成中断

}	
/***************************************
函数功能:接受重新使能
函数参数:
            DMA_Channel_TypeDef* DMAy_Channelx    //对应的DMA通道
            bytes_amount                          //字节数
函数返回值  
            void
描述:在DMA的正常模式下,DMA传输完一次之后需要重新开始传输的时候
需要先关闭该通道然后向DMA_CNDTRx寄存器写值之后再打开寄存器的值
***************************************/
void Receive_cmd(DMA_Channel_TypeDef* DMAy_Channelx,u16 bytes_amount)
{
	DMA_Cmd(DMAy_Channelx, DISABLE);  //失能USART1 TX DMA1 所指示的通道 
 	DMA_SetCurrDataCounter(DMAy_Channelx,bytes_amount);//DMA通道的DMA缓存的大小
	DMA_Cmd(DMAy_Channelx, ENABLE);  //使能USART1 TX DMA1 所指示的通道 
}

/**************下面是中断服务函数*********************/
/***************************************
函数作用:串口2中断函数
函数参数:void
函数返回值:void
***************************************/
void USART2_IRQHandler(void)
{
	 u32 temp=temp;
	 u8 i;
  if(USART_GetITStatus(USART2, USART_IT_IDLE) != RESET)
		 {
		 temp=USART2->SR;       //先读状态寄存器,然后在读数据寄存器
		 temp=USART2->DR;
	   DMA_Cmd(DMA1_Channel6, DISABLE);  //暂时关闭USART2DMA的接受通道,防止干扰
		 receive_flag=1;
		 }
}
/***************************************
函数功能:DMA接受中断服务函数
函数参数:void
函数返回值:void
***************************************/
void DMA1_Channel7_IRQHandler(void)
{
	if(DMA_GetITStatus(DMA1_IT_GL7)!=RESET)
	{
		Receive_cmd(DMA1_Channel6,20);               //开启接收
		DMA_ClearITPendingBit(DMA1_IT_GL7);          //清除所有中断标志位
		DMA_Cmd(DMA1_Channel7, DISABLE);  //暂时关闭USART2DMA的接受通道,防止干扰
		send_flag=1;   //接收完成软标志
	}
}
/**************************************************************
函数功能:获取heading角度值
入口参数:void
返回值:  void     
描述:发送 77 04 00 03 07(16进制),传感器返回heading相关数据,数据位在第四五六字节,具体见北微通信协议    
**************************************************************/
void get_heading(void)
{
	 u16 data=data;              //这样子的作用可以排除没有使用的警告
	 USART2_TX_BUF[0]=0x77;
	 USART2_TX_BUF[1]=0x04;
   USART2_TX_BUF[2]=0x00;
	 USART2_TX_BUF[3]=0x03;
   USART2_TX_BUF[4]=0x07;
	 Receive_cmd(DMA1_Channel7,5);  
	 delay_ms(1000);
	 while(send_flag);          //等待可以已发送
   while(1)	
	 {
	   if(send_flag)
		 {
			 send_flag=0;
			 break;
		 }
	 }
   while(1)	
	 {
	 if(receive_flag==1)                           //等待接受完成
	 {
					receive_flag=0;	 
		      break;
	 }
	 }	 
  
	 data=(USART2_RX_BUF[4]&0x0f)*10000;    //百位,这儿其实都是乘了100倍换成整数提高代码的效率,同时可以防止一次一次转化成浮点数带来的精度损失的问题
	 data=data+(USART2_RX_BUF[5]&0xf0>>4)*1000+(USART2_RX_BUF[5]&0x0f)*100;  		   
	 data=data+(USART2_RX_BUF[6]&0xf0>>4)*10+(USART2_RX_BUF[6]&0x0f);
	 direction_angles[0]=(data*1.0)/100;
	 if((USART2_RX_BUF[4]&0xf0)==0x10)
					 direction_angles[0]=-direction_angles[0];
}

第一次写博客,肯定有很多理解不对或者错误的地方,还请各位大佬指正,也希望能给需要的人带来一点帮助,知识需要分享才能发挥更大的价值

猜你喜欢

转载自blog.csdn.net/weixin_41271939/article/details/84797228