打印
[STM32L4]

STM32L4-双路RS485自收发通信实验

[复制链接]
236|14
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
485基本介绍

485通信协议是一种常用的半双工串行通信协议,具有抗干扰能力强,传输距离远等特点,因此在工业及自动化领域被广泛运用。


由于485属于半双工通讯,因此不能同时进行接收和发送,需要配置485控制芯片切换为发送或者接收状态。


使用特权

评论回复
沙发
有何不可0365|  楼主 | 2024-3-31 22:52 | 只看该作者
以ADM3485芯片为例:



依据真值表所示,传统的RS485收发电路通常将收发控制引脚RE和DE连接在一起,通过一个IO口的高低电平变化进行控制收发。

当MCU需要向外发送数据的时候,配置R/D引脚为高电平;

当MCU需要向内接收数据的时候,配置R/D引脚为低电平;

因此,抛开硬件层面的区别,在软件层面,RS485可以理解为串口通信+一组收发控制引脚。

那么,RS485的配置过程也与串口类似,只需要进行一些修改即可。

使用特权

评论回复
板凳
有何不可0365|  楼主 | 2024-3-31 22:52 | 只看该作者
基本配置
MCU采用STM32L431RCT6

485芯片采用ADM3485

本次使用STM32L4芯片的串口USART3和串口USART1通过两路RS485进行自收发通信实验

配置PB10,PB11为USART3 ,控制引脚PC6;

配置PA9,PA10为USART1 ,控制引脚PA11;

使用特权

评论回复
地板
有何不可0365|  楼主 | 2024-3-31 22:52 | 只看该作者
串口初始化函数
这里我配置了一个通用的初始化函数,可以根据需求配置串口,中断,中断优先级与波特率:

/*********485-通用串口初始化函数******************/
void RS485_init(USART_TypeDef* USARTX,IRQn_Type IRQn,uint8_t priority,uint32_t baud)//串口,中断,中断优先级,波特率
{
        USARTX->CR1=0;//初始化寄存器
        USARTX->CR2=0;
        USARTX->CR3=0;
       
        CLEAR_BIT(USARTX->CR1,USART_CR1_OVER8);//过采样16
        CLEAR_BIT(USARTX->CR1,USART_CR1_M0);//1个起始位8个数据位
        CLEAR_BIT(USARTX->CR1,USART_CR1_M1);
        CLEAR_BIT(USARTX->CR2,USART_CR2_STOP);//1个停止位
        CLEAR_BIT(USARTX->CR1,USART_CR1_PCE);//禁用奇偶校验控制
//        SET_BIT(USARTX->CR1,USART_CR1_PCE);//奇偶校验使能
//        SET_BIT(USARTX->CR1,USART_CR1_PS);//奇校验
        USARTX->BRR=16000000/baud;//波特率
//        SET_BIT(USARTX->CR2,USART_CR2_ABRMODE);
//        SET_BIT(USARTX->CR2,USART_CR2_ABREN);//自动波特率检测
       
    NVIC_DisableIRQ(IRQn);//关中断
        SET_BIT(USARTX->CR1,USART_CR1_RXNEIE);//设置RXNE中断
//        SET_BIT(USARTX->CR1,USART_CR1_IDLEIE);//设置IDLE中断
       
//        SET_BIT(USARTX->CR1,USART_CR1_TXEIE);//发送缓冲区空中断使能
//        SET_BIT(USARTX->CR1,USART_CR1_TCIE);//发送完成中断使能

        NVIC_SetPriority(IRQn,priority);//设置中断优先级
        NVIC_EnableIRQ(IRQn);//开中断
//        SET_BIT(USARTX->CR3,USART_CR3_DMAR);//开启DMA接收通道
//        SET_BIT(USARTX->CR3,USART_CR3_DMAT);//开启DMA发送通道
        SET_BIT(USARTX->CR1,USART_CR1_TE);//使能发送接收
        SET_BIT(USARTX->CR1,USART_CR1_RE);
        SET_BIT(USARTX->CR1,USART_CR1_UE);//开串口
        if(USARTX==USART1)
        {
                RE_DE_RX1;//初始化默认为接收状态
        }
        else if(USARTX==USART3)
        {
                RE_DE_RX3;//初始化默认为接收状态
        }
}

使用特权

评论回复
5
有何不可0365|  楼主 | 2024-3-31 22:53 | 只看该作者
波特率配置
依据芯片编程手册:



当过采样为16(OVER8=0)时,波特率为Fck(系统总线时钟)/USARTDIV(波特率分频系数)

使用特权

评论回复
6
有何不可0365|  楼主 | 2024-3-31 22:53 | 只看该作者
这里我已经将系统时钟配置为16MHZ的内部时钟HSI16:
SET_BIT(RCC->CFGR,RCC_CFGR_SWS_0);//配置时钟源为HSI16
SET_BIT(RCC->CFGR,RCC_CFGR_SW_0);
// 使能 HSI 时钟
RCC->CR |= RCC_CR_HSION;
// 等待 HSI 稳定
while (!(RCC->CR & RCC_CR_HSIRDY)) {
    // 等待 HSI 稳定
};

使用特权

评论回复
7
有何不可0365|  楼主 | 2024-3-31 22:53 | 只看该作者
因此直接往BRR寄存器中写入对应值即可:USARTX->BRR=16000000/baud;//波特率

使用特权

评论回复
8
有何不可0365|  楼主 | 2024-3-31 22:53 | 只看该作者
RS485接收中断配置函数
本次使用基于RS485的串口USART3发送数据,串口USART1接收数据。因此,使用RXNE中断时,配置USART1中断。当接收缓冲寄存器RDR非空的时候,表示串口接收到了数据,此时触发RXNE中断,在中断函数中将RDR中的值取出并储存在预设的数组中:


//USART1串口中断服务函数
//产生RXNE中断时,将usart1接收到的数据发送到缓存接收函数RX_BUF中。
void USART1_IRQHandler()
{
        //485接收数据,由USART3发送USART1接收,实现自收发
        uint8_t RES;
        if(READ_BIT(USART1->ISR,USART_ISR_RXNE))
        {
                RES=USART1->RDR &(uint16_t)0x01FF;//保存接收到的数据
                if(RX_CNT<64)
                {
                        RX_BUF[RX_CNT]=RES;//将接收到的字节每一位都存入RX_BUF里
                        RX_CNT++;//接收计数器+1
                }
        }
}

使用特权

评论回复
9
有何不可0365|  楼主 | 2024-3-31 22:54 | 只看该作者
RS485发送配置函数
RS485发送数据与串口基本相同,调用该函数的时候将485控制引脚R/D配置为发送状态,通过发送完成状态寄存器TC检测TDR寄存器中的值是否已经全部发送完成。

使用特权

评论回复
10
有何不可0365|  楼主 | 2024-3-31 22:54 | 只看该作者
在发送完成后,将R/D引脚配置回默认接收状态。
//485发送数据
void RS485_Send_Data(USART_TypeDef* USARTX,uint8_t *buf,uint8_t len)
{
        if(USARTX==USART1)
        {
                RE_DE_TX1;                        //设置为发送模式
        }
        else if(USARTX==USART3)
        {
                RE_DE_TX3;                        //设置为发送模式
        }
        uint8_t t;
          for(t=0;t<len;t++)                //循环发送数据
        {
                while(READ_BIT(USARTX->ISR,USART_ISR_TC)==0);
                USARTX->TDR=buf[t] &(uint16_t)0x01FF ;//数据放入传输数据寄存器
//                USARTX->TDR=buf[t];
        }
        while(READ_BIT(USARTX->ISR,USART_ISR_TC)==0){};
                if(USARTX==USART1)
        {
                RE_DE_RX1;                        //设置为接收模式
        }
        else if(USARTX==USART3)
        {
                RE_DE_RX3;                        //设置为接收模式
        }
}

使用特权

评论回复
11
有何不可0365|  楼主 | 2024-3-31 22:54 | 只看该作者
主函数配置
485收发引脚配置与串口配置相同,这里忽略。

开启usart1和usart3时钟:

        SET_BIT(RCC->APB2ENR,RCC_APB2ENR_USART1EN);//开usart1时钟
        SET_BIT(RCC->APB1ENR1,RCC_APB1ENR1_USART3EN);//开usart3时钟

使用特权

评论回复
12
有何不可0365|  楼主 | 2024-3-31 22:54 | 只看该作者
开启两个RS485通讯接口,波特率配置为9600:

        RS485_init(USART1,USART1_IRQn,2,9600);
        RS485_init(USART3,USART3_IRQn,1,9600);

使用特权

评论回复
13
有何不可0365|  楼主 | 2024-3-31 22:54 | 只看该作者
创建待发送数据数组,并填入要发送的数据:

这里配置发送两个字节数据0x04和0x02

uint8_t TCOM[2]={0x04,0x02};

使用特权

评论回复
14
有何不可0365|  楼主 | 2024-3-31 22:54 | 只看该作者
发送数据:

RS485_Send_Data(USART3,TCOM,3);

使用特权

评论回复
15
有何不可0365|  楼主 | 2024-3-31 22:54 | 只看该作者
最后,将接收数组RX_BUF中的两个字节数据以十进制形式分别显示在LCD液晶屏上:



可以看到接收数组中的显示数据与预先设置的发送数据一致, RS485自收发实验成功。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

31

主题

445

帖子

0

粉丝