完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
前几天接到了一个任务,用stm32f103的串口读取电子罗盘(北微姿态传感器)的数据,其实也就三个,PITCH,ROLL,HEADING三个方向上的角度,顺带复习了一下串口知识。
个人觉得熟悉一下串口协议还是很重要的,刚开始学习的时候跟着原子的视频学习还是学习的很认真的,奈何但是是为了比赛速成,所以看完视频就直接用原子的例程了,久而久之不太清楚串口通信的具体实现过程了,就知道怎么使用原子的串口例程。 下面我们先来回顾一下串口的通信例程(已经会的大佬请略过) 其实串口说到底就是一个通信工具,也就是说就是两个模块之间传输数据用的工具,那么传输数据,最重要的两个方面就是发送数据和接收数据这两个方面了,所以,我们今天也就着重从这两个方面来讲讲串口通信。 在开始之前,我觉得有个串口通信的框图我们有必要先了解一下,不要求完全看懂,但至少需要了解一些。 其实最重要的就是两(仅对于发送而言,接收同理的也是两个) 一、USARTx-》SR(状态寄存器) 就如图上所说的,控制寄存器主要就是一些使能位以及一些中断标志位,咱们可以看看参考手册,用手册说话 显然,这个寄存器中很多都是一些中断标志位。 二、USARTX-》DR(数据寄存器) 可以看出,数据寄存器只有八位有效位,所以咱们串口的数据是一字节一字节(8位)传输的。 关于数据寄存器,还有一个要注意的地方大家一定要注意。大家看数据手册。 也就是说,串口发送与接收的寄存器其实是两个寄存器,一个是TDR寄存器,一个是RDR寄存器(其实我感觉就是一个寄存器,只不过不同时候用处不一样而已,大家可以在MDk中找到USART_TypeDef这个结构体去看看定义,所以这个知识点我觉得有可能是我没有理解手册的意思),但是很爽的是我们在单片机里头的操作一概都是USARTx-》DR=0x某某; 下面咱们来看看具体的收发过程 发送 显然,按照参考手册上说的,只需要向USARTx-》DR中写数据数据就会自动发送,发送完了,SR寄存器的相应的标志就会置位。我们也可以设置相应的中断。这是单字节发送,在这个基础上,我们也可以非常轻松地实现多字节发送。下面是多字节发送程序。 /************************************************************ 函数功能:连续发送数据 函数参数 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(“han”); 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]; } |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1804 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1629 浏览 1 评论
1097 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
736 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1686 浏览 2 评论
1944浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
747浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
580浏览 3评论
603浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
565浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-28 13:52 , Processed in 0.878647 second(s), Total 76, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号