采用485来进行数据通讯,串口采用的是DMA+空闲中断来接收数据,但始终无法进入中断

1.采用485来进行数据通讯,串口采用的是DMA+空闲中断来接收数据,但始终无法进入中断!
2.采用串口通讯助手,发送数据可以正常接收到返回帧,证明器件没有问题

img

3.rs485.h

#ifndef __RS485_H_
#define __RS485_H_

#include "./SYSTEM/sys/sys.h"
#include "./BSP/PCF8574/pcf8574.h"
#include "./BSP/LED/led.h"

#define DMA_REC_LEN 50

/*IO申明*/
#define USART_TX_PIN        GPIO_PIN_2
#define USART_TX_PORT        GPIOA
#define USART_RX_PIN        GPIO_PIN_3
#define USART_RX_PORT        GPIOA

/*IO引脚定义*/
#define RS485_RE_IO 6

/* 函数申明 */
void MY_RS485_Init(uint32_t bound);
void MY_RS485_DMA_Init(void);
void RS485_Mode(uint8_t mode);
void RS485_Send_Data(uint8_t *data,uint8_t len);
void HAL_UART_ReceiveIdle(UART_HandleTypeDef *huart);
void Copy_Data(uint8_t *buf,uint16_t len);

#endif

4.rs485.c

#include "./BSP/RS485/rs485.h"

/* 采用的堵塞发送  DMA+空闲中断接收的模式来进行数据的收发 */

#define UART2_DMA_RX            1                //1使能    0失能  使用DMA接收的原理
#define UART2_DMA_TX            0                //1使能    0失能  使用DMA传输的原理   未写入
#define CHECK_NONE_ONE_STOP        1                //1个停止位  1使能    0失能
#define CHECK_NONE_TWO_STOP       0                //2个停止位
#define CHECK_EVEN                0                //偶校验
#define CHECK_ODD                0                //奇校验

UART_HandleTypeDef USART2_RS485Handler;            //串口2的句柄
DMA_HandleTypeDef  USART2_DMAHandler;            //DMA句柄

uint8_t DMA_REC_BUF[DMA_REC_LEN]={0};            //最大不能超过50个字节  DMA接收缓存区
uint16_t RX_CNT = 0;                            //计数值
uint8_t rx_end_flag = 0;                        //接收完成标志位

uint8_t Data_BackUp[DMA_REC_LEN]={0};            //数据备份接收缓存区
uint16_t datalen_backup = 0;                    //当前数据接收缓存区中的数据量

/*串口2空闲传输+DMA数据传输*/
void MY_RS485_Init(uint32_t bound){
    /*申明IO口相关参数*/
    GPIO_InitTypeDef GPIO_InitStruct;
    __HAL_RCC_USART2_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();  

//    GPIO_InitStruct.Pin = USART_TX_PIN|USART_RX_PIN;               /* TX RX引脚 */
    GPIO_InitStruct.Pin = USART_TX_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;                          /* 复用推挽输出 */
//    GPIO_InitStruct.Pull = GPIO_PULLUP;                          /* 上拉 */
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;                     /* 高速 */
    GPIO_InitStruct.Alternate = GPIO_AF7_USART2;                     /* 复用为USART2 */
    HAL_GPIO_Init(USART_TX_PORT, &GPIO_InitStruct);                  /* 初始化发送引脚 */
    
    GPIO_InitStruct.Pin  = USART_RX_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT; 
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(USART_RX_PORT, &GPIO_InitStruct); 
    
    /*配置RS485相关参数*/
    USART2_RS485Handler.Instance = USART2;
    USART2_RS485Handler.Init.BaudRate=bound;
    USART2_RS485Handler.Init.HwFlowCtl =UART_HWCONTROL_NONE;
    USART2_RS485Handler.Init.Mode =UART_MODE_TX_RX;
    USART2_RS485Handler.Init.Parity =UART_PARITY_NONE;
    USART2_RS485Handler.Init.WordLength =UART_WORDLENGTH_8B;
    
#if(CHECK_NONE_ONE_STOP== 1)
    USART2_RS485Handler.Init.StopBits =UART_STOPBITS_1;
#endif

#if(CHECK_NONE_TWO_STOP== 1)
    USART2_RS485Handler.Init.StopBits =UART_STOPBITS_2;
#endif

#if(CHECK_EVEN== 1)
    USART2_RS485Handler.Init.Parity =UART_PARITY_EVEN;
#endif

#if(CHECK_ODD== 1)
    USART2_RS485Handler.Init.Parity =UART_PARITY_ODD;
#endif

    HAL_UART_Init(&USART2_RS485Handler);
    
//    __HAL_UART_ENABLE_IT(&USART2_RS485Handler,UART_IT_RXNE);
    USART2_RS485Handler.Instance->CR1 |= USART_CR1_RXNEIE;
    RS485_Mode(0);

#if(UART2_DMA_RX== 1)
    /*连接DMA和USART_RX接受使能*/
//    __HAL_UART_ENABLE_IT(&USART2_RS485Handler,UART_IT_IDLE);                //使能串口的空闲中断
    USART2_RS485Handler.Instance->CR1 |= USART_CR1_IDLEIE;
    HAL_UART_Receive_DMA(&USART2_RS485Handler, DMA_REC_BUF, DMA_REC_LEN);    //使能DMA接收,将数据存储在DMA_REC_BUF数据缓存区中    DMA_REC_LEN是定义的DMA的最大传输数量
#endif

    /*开启接受中断&配置优先级*/
    HAL_NVIC_SetPriority(USART2_IRQn, 2, 1);
    HAL_NVIC_EnableIRQ(USART2_IRQn);
}

void MY_RS485_DMA_Init(void){
    /* 配置DMA接受句柄 */
    __HAL_RCC_DMA1_CLK_ENABLE();
    
    USART2_DMAHandler.Instance =DMA1_Stream5;
    USART2_DMAHandler.Init.Channel =DMA_CHANNEL_4;
    USART2_DMAHandler.Init.Direction =DMA_PERIPH_TO_MEMORY;
    USART2_DMAHandler.Init.PeriphInc =DMA_PINC_DISABLE;
    USART2_DMAHandler.Init.MemInc =DMA_MINC_ENABLE;
    USART2_DMAHandler.Init.PeriphDataAlignment =DMA_PDATAALIGN_BYTE;
    USART2_DMAHandler.Init.MemDataAlignment =DMA_MDATAALIGN_BYTE;
    USART2_DMAHandler.Init.Mode =DMA_CIRCULAR;
    USART2_DMAHandler.Init.Priority =DMA_PRIORITY_MEDIUM;
    USART2_DMAHandler.Init.FIFOMode =DMA_FIFOMODE_DISABLE;
        
    HAL_DMA_Init(&USART2_DMAHandler);
    __HAL_LINKDMA(&USART2_RS485Handler,hdmarx,USART2_DMAHandler);        //将USART2句柄与DMA句柄连接起来
    __HAL_DMA_ENABLE(&USART2_DMAHandler);
    
    /*  配置优先级 */
    HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn);
}

void  DMA1_Stream5_IRQHandler(void){
    HAL_DMA_IRQHandler(&USART2_DMAHandler);
}

void USART2_IRQHandler(void){
    LED1_TOGGLE();
    HAL_UART_IRQHandler(&USART2_RS485Handler);
    
    HAL_UART_ReceiveIdle(&USART2_RS485Handler);
    while(HAL_UART_Receive_DMA(&USART2_RS485Handler, DMA_REC_BUF, DMA_REC_LEN) != HAL_OK);
}

void HAL_UART_ReceiveIdle(UART_HandleTypeDef *huart){
//    uint8_t res = 0;
    
//    LED1_TOGGLE();
    if(huart->Instance == USART2){
#if(UART2_DMA_RX== 1)
        if((__HAL_UART_GET_FLAG(&USART2_RS485Handler, UART_FLAG_IDLE)) != RESET){    //接受到了一帧数据
            __HAL_DMA_DISABLE(&USART2_DMAHandler);                                    //失能DMA传输
            RX_CNT = DMA_REC_LEN - __HAL_DMA_GET_COUNTER(&USART2_DMAHandler);        //定义的最大传输数量-通道中剩余的数量=接收到数量
            Copy_Data(DMA_REC_BUF,RX_CNT);
            __HAL_UART_CLEAR_FLAG(&USART2_RS485Handler, UART_FLAG_IDLE);            //清除相关标记位
            __HAL_DMA_ENABLE(&USART2_DMAHandler);                                    //使能DMA传输
            rx_end_flag = 1;
        }
#endif
    }
}

//void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
//    uint8_t res = 0, i;
//    
////    LED1_TOGGLE();
//    if(huart->Instance == USART2){
//#if(UART2_DMA_RX== 0)
//        if((__HAL_UART_GET_FLAG(&USART2_RS485Handler, UART_FLAG_IDLE)) != RESET){    //接受到了一帧数据
//            __HAL_DMA_DISABLE(&USART2_DMAHandler);                                    //失能DMA传输
//            RX_CNT = DMA_REC_LEN - __HAL_DMA_GET_COUNTER(&USART2_DMAHandler);        //定义的最大传输数量-通道中剩余的数量=接收到数量
//            Copy_Data(DMA_REC_BUF,RX_CNT);
//            __HAL_UART_CLEAR_FLAG(&USART2_RS485Handler, UART_FLAG_IDLE);            //清除相关标记位
//            __HAL_DMA_ENABLE(&USART2_DMAHandler);                                    //使能DMA传输
//            rx_end_flag = 1;
//        }
//#endif
//    }
//}

//备份接收到的数据
void Copy_Data(uint8_t *buf,uint16_t len){
    uint16_t i;
    datalen_backup = len;
    
    for(i = 0; i < len; i++){
        Data_BackUp[i] = buf[i];
    }
}

void RS485_Send_Data(uint8_t *data,uint8_t len){
    RS485_Mode(1);
    while(__HAL_UART_GET_FLAG(&USART2_RS485Handler,UART_FLAG_TC) == RESET);            //等待传输完成
    HAL_UART_Transmit(&USART2_RS485Handler,data, len, 1000);                        //再次发送数据
    while(__HAL_UART_GET_FLAG(&USART2_RS485Handler,UART_FLAG_TC) == RESET);            //等待数据传输完成
    RX_CNT = 0;
    RS485_Mode(0);
}

/* 选择RS485的模式  1--w  0--r */
void RS485_Mode(uint8_t mode){
    PCF8574_Write_Bit(RS485_RE_IO, mode);
}

5.采用MODBUS来进行检测温度

#ifndef __RS_WS_N018_H_
#define __RS_WS_N018_H_

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/RS485/rs485.h"
#include "string.h"

/*设备地址*/
#define DEV_ADDR 0x01

/*功能码*/
#define FUN_CODE 0x03

/*寄存器地址*/
#define HUMI_ADDR  0x0000
#define TEMP_ADDR  0x0001

/*函数申明*/
uint16_t CRC_16_HEX(uint8_t *Buf, uint8_t CRC_CNT);
void N018_Send_HTCommand(void);
uint8_t N018_Scan(float *temp,float *humi);

#endif

6.

#include "./BSP/RS_WS_N018/rs_ws_n018.h"

//extern 
extern uint8_t Data_BackUp[DMA_REC_LEN];
extern uint8_t rx_end_flag;

uint8_t RS485_Send_Buf[10] = {0};
uint8_t CRC_BUF[2] ={0};
    
//CRC16校验算法 适用于16进制处理
uint16_t CRC_16_HEX(uint8_t *Buf, uint8_t CRC_CNT)
{
    unsigned long CRC_Temp;
    unsigned char i,j;
    CRC_Temp = 0xffff;
    for (i=0; i<CRC_CNT; i++) {
//        printf("%x\r\n",Buf[i]);
        CRC_Temp ^= Buf[i];
        for (j=0; j<8; j++) {
            if (CRC_Temp & 0x01)
                CRC_Temp = (CRC_Temp >>1 ) ^ 0xA001;
            else
                CRC_Temp = CRC_Temp >> 1;
        }
    }

    return(CRC_Temp>>8|CRC_Temp<<8);  
}

/**
*@brief send command of reading humidity and temperature
*/
void N018_Send_HTCommand(void){
    uint8_t addr_buf[2] ={DEV_ADDR,FUN_CODE};
    
    /*发送读取温湿度指令*/
    RS485_Send_Buf[0] = addr_buf[0];
    RS485_Send_Buf[1] = addr_buf[1];
    RS485_Send_Buf[2] = (uint8_t)HUMI_ADDR>>8;                //发送湿度首地址
    RS485_Send_Buf[3] = (uint8_t)HUMI_ADDR&0xFF;
    RS485_Send_Buf[4] = 0x00;                                //发送两个字节长度,包括了温度和湿度
    RS485_Send_Buf[5] = 0x02;
    CRC_BUF[0] = (CRC_16_HEX(RS485_Send_Buf, 6)>>8);
    CRC_BUF[1] = CRC_16_HEX(RS485_Send_Buf, 6)&0xFF;
    RS485_Send_Buf[6] = CRC_BUF[0];
    RS485_Send_Buf[7] = CRC_BUF[1];
    
    RS485_Send_Data(RS485_Send_Buf,8);
}

uint8_t N018_Scan(float *temp,float *humi){
    uint8_t ret = 1;
    uint16_t temperature = 0, humidity = 0;
    
    /* 发送读取温湿度指令 */
//    N018_Send_HTCommand();
    RS485_Send_Buf[0] = 0X01;
    RS485_Send_Buf[1] = 0X03;
    RS485_Send_Buf[2] = 0X00;                //发送湿度首地址
    RS485_Send_Buf[3] = 0X00;
    RS485_Send_Buf[4] = 0x00;                                //发送两个字节长度,包括了温度和湿度
    RS485_Send_Buf[5] = 0x02;
    RS485_Send_Buf[6] = 0XC4;
    RS485_Send_Buf[7] = 0X0B;
    RS485_Send_Data(RS485_Send_Buf,8);
    printf("send successfully\r\n");
    
     while(rx_end_flag == 0);                                            //接受到了湿度数据
    printf("receive successfully\r\n");
    if(Data_BackUp[0]==RS485_Send_Buf[0]&&Data_BackUp[1]==RS485_Send_Buf[1]){        //判断命令和地址是否一致
        CRC_BUF[0] = (CRC_16_HEX(Data_BackUp, 7)>>8);
        CRC_BUF[1] = CRC_16_HEX(Data_BackUp, 7)&0xFF;
        if(CRC_BUF[0]==Data_BackUp[7]&&CRC_BUF[1]==Data_BackUp[8]){                //判断校验码是否正确
            humidity = Data_BackUp[3]<<8|Data_BackUp[4];
            printf("humi:%d\r\n",humidity);//测试代码
            *humi = humidity % 10; 
            
            if(Data_BackUp[5]&0x80)            //温度为负
                temperature = ~(Data_BackUp[5]<<8|Data_BackUp[6])+1;
            else                        //温度为正
                temperature = Data_BackUp[5]<<8|Data_BackUp[6];
            *temp = temperature % 10;
            
            ret = 0;
        }
    }
    printf("receive successfully\r\n");
    return ret;
}

中断被禁止:检查中断是否被禁止,如果被禁止,请确保在正确的位置启用中断。
硬件连接问题:确保您的串口硬件连接正确,且没有任何松动或损坏。
串口配置问题:检查您的串口配置是否正确。例如,波特率、数据位、停止位、校验位等设置是否正确。
空闲中断程序问题:检查您的空闲中断程序是否正确编写。它应该能够正确地接收数据并在中断发生时执行相应的操作。

  • 这篇博客: 关于RS485的DMA发送,以及EN使能脚的自动切换中的 第二种问题分为硬件解决和软件解决: 部分也许能够解决你的问题, 你可以仔细阅读以下内容或跳转源博客中阅读:
  • 硬件解决:A B间并联10k电阻,加大放电速度,使得A B的电平变化速度加快6倍左右,RX上反射的波形持续时间缩短,软件将不会触发串口接收中断。以下为并联10k电阻,和无电阻的对比:

    10K电阻并联

     

    无并联电阻

    软件解决:再发送未完成之前,禁止接收中断,从而屏蔽掉反射波形的影响。

    ST官方都有APPNOTE指导的(对于UART没有RS485功能的单片机型号而言):
    1、启动DMA前,先关闭UART发送完成中断,并清除发送完成中断标志;
    2、在DMA传输完成中断函数中,开启UART发送完成中断;
    3、在UART发送完成中断函数中,切换RS485为接收态;

    (可以在发送的时候将串口接收直接失能,在串口发送完成中断中再使能)

     

    DMA中断时,只表示需要传送的所有数据字节全部传送到串口的发送数据寄存器中了。
    此时串口实际上还有2个字节并未发送完成,数据寄存器和移位寄存器中的2个字节还需要发送,并不能关闭串口发送。
    同理,如果是485切换方向,必须要等到发送完成,也就是移位寄存器发送完成-TC标志置位。

     

    你看串口的硬件结构就明白了,TXE指的是发送缓冲器DR空,TC指的是SHIFT移位寄存器空。
    DMA完成只是代表把最后一个字节送到DR寄存器里面了,此时SHIFT移位寄存器有1个字节正在开始发送,
    DR寄存器里面有一个字节等待发送,所以就是2个字节未发送完成。

    void Drv_Usart2TxStream(uint8_t const data[], uint32_t num)
    {
      if(!data || !num){
        return;
      }
    
      if(is_tx_busy){
        return;
      }
    
      /* 设置发送忙标志,表示占用硬件 */
      is_tx_busy = TRUE;
      
      /* 清除串口发送完成中断标志 */
      USART_ITConfig(USART2, USART_IT_TC, DISABLE);
      USART_ClearFlag(USART2, USART_FLAG_TC);
    
      /* 启动DMA发送 */
      DMA1->IFCR = DMA_IFCR_CGIF7;
      DMA1_Channel7->CCR   = ((0<<12)|(0<<8)|DMA_CCR7_MINC|DMA_CCR7_DIR);
      DMA1_Channel7->CCR  |= (DMA_CCR7_TCIE|DMA_CCR7_TEIE);
      DMA1_Channel7->CPAR  = (uint32_t)&USART2->DR;
      DMA1_Channel7->CMAR  = (uint32_t)data;
      DMA1_Channel7->CNDTR = (uint16_t)num;
      DMA1_Channel7->CCR  |= DMA_CCR7_EN;
    }
    
    void DMA1_Channel7_IRQHandler(void)
    {
      uint32_t isr = DMA1->ISR;
      DMA1->IFCR = DMA_IFCR_CGIF7;
      if(isr & (DMA_ISR_TEIF7|DMA_ISR_TCIF7)){
        DMA1_Channel7->CCR = 0;
        USART_ITConfig(USART2, USART_IT_TC, ENABLE);
      }
    }
    
    void USART2_IRQHandler(void)
    {
      // 最后一字节发送完成中断
      if((USART2->CR1 & USART_CR1_TCIE) && (USART2->SR & USART_SR_TC)){
        USART_ITConfig(USART2, USART_IT_TC, DISABLE);
        is_tx_busy = 0;
        // 至此,最后一字节也发送完成了
      }
    }