2、串口通信
通信就是各种设备之间的交流, 如电脑连接键盘、鼠标、打印机之类的。
计算机领域中的通信有两种:串行通信、并行通信。
- 并行通信
-传输原理:数据各个位同时传输。
-优点:速度快
-缺点:占用引脚资源多
- 串行通信
-传输原理:数据按位顺序传输。
-优点:占用引脚资源少
-缺点:速度相对较慢
串行通信的通信方向:
单工:数据传输只支持数据在一个方向上传输
半双工:允许数据在两个方向上传输,但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;
全双工:允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。
三种寄存器分析串行通信:
数据寄存器:USART_DR数据寄存器
状态寄存器:USART_SR状态寄存器
控制寄存器:USART_BRR波特率寄存器
除了这些寄存器外,我们还要用到AFR寄存器:
STM32 中的 AFR 寄存器是 GPIO (General Purpose Input/Output) 控制器寄存器,用于配置引脚的复用功能。AFR 代表 Alternate Function Register,它允许你将 GPIO 引脚配置为除了标准数字输入/输出之外的其他功能,例如串口通信、定时器输入/输出等。
配置 AFR 寄存器通常包括以下步骤:
-
选择要配置的引脚。
-
确定要分配给引脚的复用功能编号(通常是一个数字,代表不同的复用功能)。
-
将所选引脚的 AFR 寄存器位设置为选定的复用功能编号。
具体的配置步骤和使用方法会根据你使用的 STM32 型号和开发环境而有所不同,通常需要查阅相应的参考手册、数据表或开发工具文档来获取详细的信息。根据不同的 STM32 型号,AFR 寄存器的名称和配置方式可能会有所不同。
串口寄存器的结构体定义:
这里的寻址方法和gpio相似利用偏移量进行查表寻址
在使用某个串口的时候可以这样写USART + 端口号:
函数:
uart_init 初始化
uart_sendl 发送1个字节的数据
uart_sendN 发送n个字节的数据
uart_rel 接受一个字节的数据
uint8_t uart_is_uartNo(uint8_t uartNo) //就是检查一下输入的端口号的合法性 { if(uartNo < UART_1 || uartNo > UART_3) return 0; else return 1; }
//====================================================================== //函数名称:uart_init //功能概要:初始化uart模块 //参数说明:uartNo:串口号:UART_1、UART_2、UART_3 // baud:波特率:300、600、1200、2400、4800、9600、19200、115200... //函数返回:无 //====================================================================== void uart_init(uint8_t uartNo, uint32_t baud_rate) { uint16_t usartdiv; //BRR寄存器应赋的值 //判断传入串口号参数是否有误,有误直接退出 if(!uart_is_uartNo(uartNo)) //如果输入的端口号不合法,直接返回 { return; } //开启UART模块和GPIO模块的外围时钟,并使能引脚的UART功能 switch(uartNo) { case UART_1: //若为串口1 #ifdef UART1_GROUP //依据选择使能对应时钟,并配置对应引脚为UART_1 switch(UART1_GROUP) { case 0: //使能USART1和GPIOA时钟 RCC->APB2ENR |= RCC_APB2ENR_USART1EN; RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN; //使能PTA9,PTA10为USART(Tx,Rx)功能 GPIOA->MODER &= ~(GPIO_MODER_MODE9|GPIO_MODER_MODE10); GPIOA->MODER |= (GPIO_MODER_MODE9_1|GPIO_MODER_MODE10_1); GPIOA->AFR[1] &= ~(GPIO_AFRH_AFSEL9|GPIO_AFRH_AFSEL10); GPIOA->AFR[1] |= ((GPIO_AFRH_AFSEL9_0 | GPIO_AFRH_AFSEL9_1 | GPIO_AFRH_AFSEL9_2) |(GPIO_AFRH_AFSEL10_0 | GPIO_AFRH_AFSEL10_1 | GPIO_AFRH_AFSEL10_2)); break; case 1: //使能USART1和GPIOB时钟 RCC->APB2ENR |= RCC_APB2ENR_USART1EN; RCC->AHB2ENR |= RCC_AHB2ENR_GPIOBEN; //使能PTB6,PTB7为USART(Tx,Rx)功能 GPIOB->MODER &= ~(GPIO_MODER_MODE6|GPIO_MODER_MODE7); GPIOB->MODER |= (GPIO_MODER_MODE6_1|GPIO_MODER_MODE7_1); GPIOB->AFR[0] &= ~(GPIO_AFRL_AFSEL6|GPIO_AFRL_AFSEL7); GPIOB->AFR[0] |= ((GPIO_AFRL_AFSEL6_0 | GPIO_AFRL_AFSEL6_1 | GPIO_AFRL_AFSEL6_2) |(GPIO_AFRL_AFSEL7_0 | GPIO_AFRL_AFSEL7_1 | GPIO_AFRL_AFSEL7_2)); break; default: break; } #endif break; case UART_2: //若为串口2 #ifdef UART2_GROUP //依据选择使能对应时钟,并配置对应引脚为UART_2 switch(UART2_GROUP) { case 0: //使能USART2和GPIOA时钟 RCC->APB1ENR1 |= RCC_APB1ENR1_USART2EN; RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN; //使能PTA2,PTA3为USART(Tx,Rx)功能 GPIOA->MODER &= ~(GPIO_MODER_MODE2|GPIO_MODER_MODE3); GPIOA->MODER |= (GPIO_MODER_MODE2_1|GPIO_MODER_MODE3_1); GPIOA->AFR[0] &= ~(GPIO_AFRL_AFSEL2|GPIO_AFRL_AFSEL3); GPIOA->AFR[0] |= ((GPIO_AFRL_AFSEL2_0 | GPIO_AFRL_AFSEL2_1 | GPIO_AFRL_AFSEL2_2) |(GPIO_AFRL_AFSEL3_0 | GPIO_AFRL_AFSEL3_1 | GPIO_AFRL_AFSEL3_2)); break; default: break; } #endif break; case UART_3: //若为串口3 #ifdef UART3_GROUP //依据选择使能对应时钟,并配置对应引脚为UART_3 switch(UART3_GROUP) { case 0: //使能USART3和GPIOB时钟 RCC->APB1ENR1 |= RCC_APB1ENR1_USART3EN; RCC->AHB2ENR |= RCC_AHB2ENR_GPIOBEN; //使能PTB10,PTB11为USART(Tx,Rx)功能 GPIOB->MODER &= ~(GPIO_MODER_MODE10|GPIO_MODER_MODE11); GPIOB->MODER |= (GPIO_MODER_MODE10_1|GPIO_MODER_MODE11_1); GPIOB->AFR[1] &= ~(GPIO_AFRH_AFSEL10|GPIO_AFRH_AFSEL11); GPIOB->AFR[1] |= ((GPIO_AFRH_AFSEL10_0 | GPIO_AFRH_AFSEL10_1 | GPIO_AFRH_AFSEL10_2) |(GPIO_AFRH_AFSEL11_0 | GPIO_AFRH_AFSEL11_1 | GPIO_AFRH_AFSEL11_2)); break; case 1: //使能USART3和GPIOC时钟 RCC->APB1ENR1 |= RCC_APB1ENR1_USART3EN; RCC->AHB2ENR |= RCC_AHB2ENR_GPIOCEN; //使能PTC10,PTC11为USART(Tx,Rx)功能 GPIOC->MODER &= ~(GPIO_MODER_MODE10|GPIO_MODER_MODE11); GPIOC->MODER |= (GPIO_MODER_MODE10_1|GPIO_MODER_MODE11_1); GPIOC->AFR[1] &= ~(GPIO_AFRH_AFSEL10|GPIO_AFRH_AFSEL11); GPIOC->AFR[1] |= ((GPIO_AFRH_AFSEL10_0 | GPIO_AFRH_AFSEL10_1 | GPIO_AFRH_AFSEL10_2) |(GPIO_AFRH_AFSEL11_0 | GPIO_AFRH_AFSEL11_1 | GPIO_AFRH_AFSEL11_2)); break; default: break; } #endif break; } //暂时禁用UART功能 -- 防止它胡乱发送乱码 USART_ARR[uartNo-1]->CR1 &= ~USART_CR1_UE; //暂时关闭串口发送与接收功能 USART_ARR[uartNo-1]->CR1 &= ~(USART_CR1_TE_Msk|USART_CR1_RE_Msk); //配置串口波特率 if((USART_ARR[uartNo-1]->CR1 & USART_CR1_OVER8_Msk) == USART_CR1_OVER8_Msk) usartdiv = (uint16_t)((SystemCoreClock/baud_rate)*2); else usartdiv = (uint16_t)(SystemCoreClock/baud_rate); USART_ARR[uartNo-1]->BRR = usartdiv; //初始化控制寄存器和中断状态寄存器、清标志位 USART_ARR[uartNo-1]->ISR = 0; USART_ARR[uartNo-1]->CR2 &= ~(USART_CR2_LINEN | USART_CR2_CLKEN); USART_ARR[uartNo-1]->CR3 &= ~(USART_CR3_SCEN | USART_CR3_HDSEL | USART_CR3_IREN); //启动串口发送与接收功能 USART_ARR[uartNo-1]->CR1 |= (USART_CR1_TE|USART_CR1_RE); //开启UART功能 USART_ARR[uartNo-1]->CR1 |= USART_CR1_UE; }
//函数名称:uart_send1 //参数说明:uartNo: 串口号:UART_1、UART_2、UART_3 // ch:要发送的字节 //函数返回:函数执行状态:1=发送成功;0=发送失败。 //功能概要:串行发送1个字节 //======================================================================
// 之后的sendN和sendstring都是以这个函数为基础 uint8_t uart_send1(uint8_t uartNo, uint8_t ch) { //发送操作是由硬件完成的,我们把要发送的数据放到对应的寄存器TDR中 uint32_t t; //判断传入串口号参数是否有误,有误直接退出 if(!uart_is_uartNo(uartNo)) { return 0; } for (t = 0; t < 0xFBBB; t++)//查询指定次数 -- 如果在0xFBBB次循环之内发送完了,t就小于0xFBBB此时认定发送成功 {
//ISR寄存器是中断和状态寄存器,USART_ISR_TXE_Msk是一个位掩码,这个位掩码可以用来探测发送寄存器是否为空
//检测方法: 两个寄存器相与
//发送缓冲区为空则发送数据 if ( USART_ARR[uartNo-1]->ISR & USART_ISR_TXE_Msk ) {
// 如果为空,将数据放到,TDR寄存器中,它自己就发送了 USART_ARR[uartNo-1]->TDR = ch; break; } }//end for if (t >= 0xFBBB) return 0; //发送超时,发送失败 else return 1; //成功发送 }
//以下两个函数都是以send1为基础
//====================================================================== //函数名称:uart_sendN //参数说明:uartNo: 串口号:UART_1、UART_2、UART_3 // buff: 发送缓冲区 // len:发送长度 //函数返回: 函数执行状态:1=发送成功;0=发送失败 //功能概要:串行 接收n个字节 //====================================================================== uint8_t uart_sendN(uint8_t uartNo ,uint16_t len ,uint8_t* buff) { uint16_t i; //判断传入串口号参数是否有误,有误直接退出 if(!uart_is_uartNo(uartNo)) { return 0; } for (i = 0; i < len; i++) { if (!uart_send1(uartNo, buff[i])) //发送一个字节数据,失败则跳出循环 { break; } } if(i<len) return 0; //发送出错 else return 1; //发送出错 } //====================================================================== //函数名称:uart_send_string //参数说明:uartNo:UART模块号:UART_1、UART_2、UART_3 // buff:要发送的字符串的首地址 //函数返回: 函数执行状态:1=发送成功;0=发送失败 //功能概要:从指定UART端口发送一个以'\0'结束的字符串 //====================================================================== uint8_t uart_send_string(uint8_t uartNo, uint8_t* buff) { uint16_t i = 0; uint8_t *buff_ptr = (uint8_t *)buff; //定义指针指向要发送字符串首地址 //判断传入串口号参数是否有误,有误直接退出 if(!uart_is_uartNo(uartNo)) { return 0; } for(i = 0; buff_ptr[i] != '\0'; i++) //遍历字符串里的字符 { if (!uart_send1(uartNo,buff_ptr[i]))//发送指针对应的字符 return 0; //发送失败,返回 } return 1; //发送成功 }
在使用串口接收函数时,可以先判断有没有接受到中断,再进行接收操作。
//====================================================================== //函数名称:uart_get_re_int //参数说明:uartNo: 串口号 :UART_1、UART_2、UART_3 //函数返回:接收中断标志 1=有接收中断;0=无接收中断 //功能概要:获取串口接收中断标志,同时禁用发送中断 //====================================================================== uint8_t uart_get_re_int(uint8_t uartNo) { //判断传入串口号参数是否有误,有误直接退出 if(!uart_is_uartNo(uartNo)) { return 0; } //获取接收中断标志,需同时判断RXNE和RXNEIE if(((USART_ARR[uartNo-1]->ISR & USART_ISR_RXNE_Msk) == USART_ISR_RXNE_Msk) && ((USART_ARR[uartNo-1]->CR1 & USART_CR1_RXNEIE_Msk) == USART_CR1_RXNEIE_Msk)) return 1; else return 0; }
//在使用时可以进行嵌套
if(uart_get_re_int(UART_1)){
if(ch = uart_re1(UART_1, &flag), flag){
... ...
}
}
//====================================================================== //函数名称:uart_re1 //参数说明:uartNo: 串口号:UART_1、UART_2、UART_3 // *fp:接收成功标志的指针:*fp=1:接收成功;*fp=0:接收失败 //函数返回:接收返回字节 //功能概要:串行接收1个字节 //====================================================================== uint8_t uart_re1(uint8_t uartNo,uint8_t *fp) { uint32_t t; uint8_t dat; //判断传入串口号参数是否有误,有误直接退出 if(!uart_is_uartNo(uartNo)) { *fp=0; return 0; } for (t = 0; t < 0xFBBB; t++)//查询指定次数 { //判断接收缓冲区是否满 if (USART_ARR[uartNo-1]->ISR & USART_ISR_RXNE_Msk) { dat=USART_ARR[uartNo-1]->RDR; //获取数据,清接收中断位 *fp = 1; //接收成功 break; } }//end for if(t >= 0xFBBB) { dat = 0xFF; *fp = 0; //未收到数据 } return dat; //返回接收到的数据 }
//====================================================================== //函数名称:uart_reN //参数说明:uartNo: 串口号:UART_1、UART_2、UART_3 // buff: 接收缓冲区 // len:接收长度 //函数返回:函数执行状态 1=接收成功;0=接收失败 //功能概要:串行 接收n个字节,放入buff中 //====================================================================== uint8_t uart_reN(uint8_t uartNo ,uint16_t len ,uint8_t* buff) { uint16_t i; uint8_t flag = 0; //判断传入串口号参数是否有误,有误直接退出 if(!uart_is_uartNo(uartNo)) { return 0; } //判断是否能接受数据 for (i = 0; i < len && flag==1; i++) { buff[i] = uart_re1(uartNo, &flag); //接受数据 } if (i < len) return 0; //接收失败 else return 1; //接收成功 }
组帧函数(Frame Building Function)是指在通信协议中用于构建数据帧的函数。数据帧是数据通信中的一种基本单元,通常包括数据字段、控制信息和校验信息等。组帧函数的主要作用是将要传输的数据封装成符合通信协议要求的帧,以便发送给接收方。
个人理解:帧就是数据传输中的基本单元,双方根据特殊的约定设计帧格式,以便更方便的进行数据的传输。
我们的实验帧格式:帧头 + 长度 + 数据 + 帧尾
//====================================================================== //中断服务程序名称:UART_USER_Handler //触发条件:UART_USE串口收到一个字节触发 //功 能:收到一个字节,直接返回该字节 //备 注:进入本程序后,可使用uart_get_re_int函数可再进行中断标志判断 // (1-有UART接收中断,0-没有UART接收中断) //====================================================================== void UART_User_Handler(void) { uint8_t ch,flag; DISABLE_INTERRUPTS; //关总中断 //----------------------------------------------------------------------- ch = uart_re1(UART_User, &flag); //调用接收一个字节的函数,清接收中断位 //调用内部函数CreateFrame进行组帧 // uart_send1(UART_User,ch);//回发接收到的字节 if(CreateFrame(ch,g_uart_recvBuf)!=0) //组帧成功 { uart_send_string(UART_User," 组帧成功!\n"); } ENABLE_INTERRUPTS; //开总中断 }
//内部调用函数 //=========================================================================== //函数名称:CreateFrame //功能概要:组建数据帧,将待组帧数据加入到数据帧中 //参数说明:Data: 待组帧数据 // buffer: 数据帧变量 //函数返回:组帧状态 0-组帧未成功,1-组帧成功 //备注:十六进制数据帧格式 // 帧头 + 数据长度 + 有效数据 + 帧尾 // FrameHead + len + 有效数据 + FrameTail //=========================================================================== #define FrameHead (0x50) //帧头 ASCII码对应P #define FrameTail (0x43) //帧尾 ASCII码对应C //P4openC uint8_t CreateFrame(uint8_t Data,uint8_t * buffer) { static uint8_t frameLen=0; //帧的计数器 uint8_t frameFlag; //组帧状态 frameFlag=0; //组帧状态初始化 //根据静态变量frameCount组帧 switch(frameLen) { case 0: //第一个数据 { if (Data==FrameHead) //收到数据是帧头FrameHead { buffer[0]=Data; frameLen++; frameFlag=0; //组帧开始 } break; } case 1: //第二个数据,该数据是随后接收的数据个数 { buffer[1]=Data-0x30; frameLen++; break; } default: //其他情况 { //第二位数据是有效数据长度,根据它接收余下的数据直到帧尾前一位 if(frameLen>=2 && frameLen<=(buffer[1] + 1)) { buffer[frameLen]=Data; frameLen++; break; } //若是末尾数据则执行 if(frameLen>=(buffer[1]+2)) { if (Data==FrameTail) //若是帧尾 { buffer[frameLen]=Data; //将帧尾存入缓冲区 frameFlag=1; //组帧成功 } frameLen=0 ; //计数清0,准备重新组帧 break; } } } //switch_END return frameFlag; //返回组帧状态 } /* 知识要素: 1.本文件中的中断处理函数调用的均是相关设备封装好的具体构件,在更换芯片 时,只需保证设备的构件接口一致,即可保证本文件的相关中断处理函数不做任何 更改,从而达到芯片无关性的要求。 */
// 这个不用输入len 直接 S...E即可
#define FrameHead 'S' #define FrameTail 'E' //P4openC uint8_t CreateFrame(uint8_t Data,uint8_t * buffer) { static uint8_t frameLen=0; //帧的计数器 uint8_t frameFlag; //组帧状态 frameFlag=0; //组帧状态初始化 //根据静态变量frameLen组帧 switch(frameLen) { case 0: //第一个数据 { if (Data==FrameHead) //收到数据是帧头FrameHead { buffer[0]=Data; frameLen++; frameFlag=0; //组帧开始 } break; } default: //其他情况 { //如果接收到的不是帧尾 if(frameLen>=1 && Data!=FrameTail) { buffer[frameLen]=Data; frameLen++; break; } //若是末尾数据则组帧成功 if(Data==FrameTail) { buffer[frameLen]=Data; frameFlag=1; //组帧成功 frameLen=0; //计数清0,准备重新组帧 break; } } } return frameFlag; //返回组帧状态 }