【STM32】串口收发驱动Drv_Uart|学习笔记
一、什么事串口?
大家常说串口,其实串口有很多种UART,SPI,IIC都是串口,一般大家口中的串口就是UART(Universal Asynchronous Receiver/Transmitter),STM32上集成了UART的控制器,所以我们通过简单的配置就可以实现UART通信的功能。当然光有控制器可以在单板间通信,但大部分的应用场景都是需要远距离的抗干扰传输,这时就需要做电平转换,,目前工业上常用的串口屏,串口透传zigbee,诸如此类的设备都会用到标准的串行接口,所以单板上一般会加一个收发器,来实现电平转换,常用的串行接口就是大家常说的232,485,422等。
对于STM32来说不同的接口控制方法基本类似(就两线制来说),485会多一条读写的控制引脚,因为它是半双工,不能同时读写。
二、怎样使用它?
1.串口外设使能,GPIO使能
RCC_APB2PeriphClockCmd();
2.串口复位
USART_DeInit();
3.GPIO模式设置
GPIO_Init();
GPIO_PinAFConfig();
4.串口参数初始化
USART_Init();
5.开启中断并初始化NVIC
NVIC_Init();
USART_ITConfig();
6.使能串口
UART_Cmd();
7.编写中断处理函数
USARTx_IRQHandler();
8.串口数据收发
void USART_SendData();
u8 USART_ReceiveData();
贴一个配置代码
这是串口控制器结构体
1 2 3 4 5 6 7 8 9 10 | typedef struct Com_Manager { u8 Status; u8 Send_Buf[256]; u16 TxByte_Counter; u16 Stop_Byte; u8 Recv_Buf[256]; u16 RxByte_Counter; u16 OverTime_cnt; }Com_Manager; |
函数实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | void InitUart4(u32 bdr) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4,ENABLE); //IO时钟UART时钟不用说一定都要开启先 GPIO_PinAFConfig(GPIOC,GPIO_PinSource10,GPIO_AF_UART4); GPIO_PinAFConfig(GPIOC,GPIO_PinSource11,GPIO_AF_UART4); //F2系列必须有这一句去开启IO的复用功能 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOE,&GPIO_InitStructure); UART4_DIR_RX(); //这里哦那个 USART_DeInit(UART4); USART_InitStructure.USART_BaudRate = bdr; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_HardwareFlowControl =USART_HardwareFlowControl_None; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_Mode = USART_Mode_Tx|USART_Mode_Rx; USART_Init(UART4,&USART_InitStructure); //UART配置 NVIC_InitStructure.NVIC_IRQChannel = UART4_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_Init(&NVIC_InitStructure); //NVIC配置 USART_ITConfig(UART4,USART_IT_RXNE,ENABLE); //可以查参考手册中关于串口中断的部分,RXNE指的是接收完成中断,每当接收到一个字节就进一次中断。 USART_Cmd(UART4,ENABLE); //启动UART } void UART4_IRQHandler() //UART USART要注意区分 { if ((USART_GetITStatus(UART4,USART_IT_RXNE))&&((Com[4].Status&0x06) == 0x00)) { //建立接收 Com[4].Status |= COM_RECEIVING; Com[4].Recv_Buf[Com[4].RxByte_Counter] = USART_ReceiveData(UART4); Com[4].RxByte_Counter++; Com[4].OverTime_cnt = 0; if (Com[4].RxByte_Counter > 255) { Com[4].RxByte_Counter = 0; Com[4].Status = COM_RECVFULL; //没想好怎么处理 } } } u8 Drv_Uart_Async_Send(Com_Manager* port,u8* send_buf,u16 buf_size) { if ((buf_size < 256)&&((port->Status&0x03) == 0x00)) { //建立发送 port->Status |= COM_SENDING; port->Stop_Byte = buf_size; port->TxByte_Counter = 0; memcpy (port->Send_Buf,send_buf,buf_size); return 1; } else { //错误类型分类返回 return 0; } } u16 Drv_Uart_Async_Recv(Com_Manager* port,u8 *recv_buf) { u16 counter_saver; if ((port->Status&COM_RECVCOMPLETE) == COM_RECVCOMPLETE) { if (port->RxByte_Counter > 0) { counter_saver = port->RxByte_Counter; memcpy (recv_buf,port->Recv_Buf,port->RxByte_Counter); port->RxByte_Counter = 0; port->OverTime_cnt = 0; port->Status &= ~COM_RECVCOMPLETE; return counter_saver; } else { return 0; } } else { return 0; } } |
发送这里用到了定时器,我用了50us来刷新是否有新建的发送任务及正在发送的任务,累计500us没有收到数据认为接收完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | void Drv_Uart_50us_do() { //Com2循环发送处理************************************************************ if ((Com[2].Status&COM_SENDING) == COM_SENDING) { if (USART_GetFlagStatus(USART2,USART_FLAG_TC) != RESET) { UART2_DIR_TX(); USART_SendData(USART2,Com[2].Send_Buf[Com[2].TxByte_Counter++]); if (Com[2].TxByte_Counter > Com[2].Stop_Byte) { UART2_DIR_RX(); //非常重要 坑了我一天 发送完必须复位RE 否则进不了接收中断 Com[2].Status &= ~COM_SENDING; Com[2].TxByte_Counter = 0; Com[2].Stop_Byte = 0; } } } //Com2接收超时处理 else if ((Com[2].Status&COM_RECEIVING) == COM_RECEIVING) { Com[2].OverTime_cnt++; if (Com[2].OverTime_cnt >= 10) { Com[2].Status |= COM_RECVCOMPLETE; Com[2].Status &= ~COM_RECEIVING; //5ms仍未接收到数据认为接收完成 } } } |
我这里用了一种状态处理机制,来保证485的半双工正常工作,简单来说就是收的时候不能发送发的时候不能接收,收发互斥。
但是这样做存在一个问题就是如果接受的数据没有及时处理,那么会造成一个死锁,接下来优化考虑用堆栈来接收数据,把接收到的数据压入栈,需要读的时候弹栈。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!