基于UCOSII的RS485通信(STM32F107)

一、实现效果

        基于ucosii实时操作系统的RS485通信,采用USART + DMA进行收发,

 二、开发环境

  • 开发工具:KEIL V5
  • 开发板: STM32f107RC
  • 采用方式:USART + DMA
  • 使用系统:UCOSII

三、RS485部分原理

        在RS-485通讯网络中,节点中的串口控制器使用RXTX信号线连接到收发器上,而收发器通过差分线连接到网络总线,串口控制器与收发器之间一般使用TTL信号传输,收发器与总线则使用差分信号来传输。

        发送数据时,串口控制器的TX信号经过收发器转换成差分信号传输到总线上,

        而接收数据时,收发器把总线上的差分信号转化成TTL信号通过RX引脚传输到串口控制器中。

        MCU管脚输出TTL电平,TTL电平的意思是,当MCU管脚输出0电平时,一般情况下电压是0V,当MCU管脚输出1电平时,电压是5V。因TTL电平的是由一条信号线,一条地线产生,信号线上的干扰信号会跟随有效信号传送到接收端,使得有效信号受到干扰,485通讯实际上是把MCU出来的TTL电平通过硬件层的一个转换器芯片进行转换

基于UCOSII的RS485通信(STM32F107)_第1张图片

        RS-485通讯网络的最大传输距离可达1200米,总线上可挂载128个通讯节点,而由于RS-485网络只有一对差分信号线,它使用差分信号来表达逻辑,AB两线间的电压差为-6V~-2V时表示逻辑1,当电压差为+2V~+6V表示逻辑0,在同一时刻只能表达一个信号,所以它的通讯是半双工形式的。

基于UCOSII的RS485通信(STM32F107)_第2张图片

        在单个实验板中,作为串口控制器的STM32USART外设引出TXRX两个引脚与RS-485收发器MAX485相连,收发器使用它的AB引脚连接到RS-485总线网络中。为了方便使用,我们每个实验板引出的AB之间都连接了1120欧的电阻作为RS-485总线的端电阻,所以要注意如果要把实验板作为一个普通节点连接到现有的RS-485总线时,是不应添加该电阻的

        MAX485芯片中有"RE"和"DE"两个引脚,用于控制485芯片的收发工作状态的,当RE引脚为低电平时,485芯片处于接收状态,当DE引脚为高电平时芯片处于发送状态。实验板中使用了STM32PD11直接连接到这两个引脚上,所以通过控制PD11的输出电平即可控制485的收发状态。

基于UCOSII的RS485通信(STM32F107)_第3张图片

 实验板之间AA连接,BB连接即可

基于UCOSII的RS485通信(STM32F107)_第4张图片

四、配置操作

 建立了5个任务

  任务名                                                 优先级
            APP_TASK_START_PRIO              2            主任务              
            Task_Com4_PRIO                           4            COM4通信任务
          
         当然还包含了系统任务:
            OS_TaskIdle                  空闲任务-----------------优先级最低
            OS_TaskStat                  统计运行时间的任务-------优先级次低 

 4.1 主任务建立

 //建立主任务, 优先级最高  建立这个任务另外一个用途是为了以后使用统计任务
os_err = OSTaskCreate((void (*) (void *)) App_TaskStart, (void *) 0,   //指向任务代码的指针                                 
                                  (void *) 0,    //任务开始执行时,传递给任务的参数的指针                                       
               (OS_STK *) &App_TaskStartStk[APP_TASK_START_STK_SIZE - 1], //分配给任务的堆栈的栈顶指针   从顶向下递减   
               (INT8U) APP_TASK_START_PRIO);    //分配给任务的优先级  
static  void App_TaskStart(void* p_arg) 
{
   (void) p_arg
 
   //使能ucos 的统计任务
   
#if (OS_TASK_STAT_EN > 0)
    //----统计任务初始化函数 
    OSStatInit();                                      /* Determine CPU capacity.   */                         
 #endif
   
   //建立其他的任务  
   App_TaskCreate();

   while (1)
   {       
	  //1秒一次循环
      OSTimeDlyHMSM(0, 0,1, 0);
    }
 }

4.2 其他任务建立

static  void App_TaskCreate(void)
  {
     //CPU_INT08U os_err;
		
    //Com1_SEM=OSSemCreate(1);            //建立串口4中断的信号量
    Com4_MBOX = OSMboxCreate((void *) 0);             //建立串口4中断的消息邮箱
     
     //串口4接收及发送任务---------------------------------------------------------    
     OSTaskCreateExt(Task_Com4,                    //指向任务代码的指针
                        (void *)0,                 //任务开始执行时,传递给任务的参数的指针
                     (OS_STK *)&Task_Com4Stk[Task_Com4_STK_SIZE-1],//分配给任务的堆栈的栈顶指针   从顶向下递减
                     Task_Com4_PRIO,               //分配给任务的优先级
					 Task_Com4_PRIO,               //预备给以后版本的特殊标识符,在现行版本同任务优先级
                     (OS_STK *)&Task_Com4Stk[0],   //指向任务堆栈栈底的指针,用于堆栈的检验
                     Task_Com4_STK_SIZE,           //指定堆栈的容量,用于堆栈的检验
                     (void *)0,                   //指向用户附加的数据域的指针,用来扩展任务的任务控制块
				     OS_TASK_OPT_STK_CHK|OS_TASK_OPT_STK_CLR); //选项,指定是否允许堆栈检验,是否将堆栈清0,任务是否要进行浮点运算等等。 
}

4.2.1 建立子任务——串口通信的任务

串口通信的任务:这里采用消息邮箱进行消息传递,

  • 在建立其他任务App_TaskCreate(void)的开始就首先建立串口的消息邮箱:Com4_MBOX=OSMboxCreate((void *) 0);
  • 然后在串口通信的任务中进入循环后就一直等待消息邮箱的信息(第8行),如果没有消息过来就一直等待,在此期间其他任务可以进行,一旦有消息发送过来,由于串口通信的优先级较高,就能很快响应,根据收到的消息msg来自定义。
  • 这里因为串口接收要用到中断,所以下面就说说串口通信的接收中断部分。
static  void Task_Com4(void *p_arg)
{    
     INT8U err;
		 int i;
    unsigned char * msg;
     (void)p_arg;                        
     while(1)
	{
          
       //OSSemPend(Com1_SEM,0,&err);          //等待串口接收指令成功的信号量
       msg=(unsigned char *)OSMboxPend(Com4_MBOX, 0,&err);         //等待串口接收指令成功的邮箱信息
		
        //输出邮箱信息的前10个数据
      if(msg != NULL)
	  {
			for(i = 0; i < 2; i++)
			{
				G_u8Usart1SendBuf[i] = 0x10;
			}
					
			USART_DMA_SendStart(DMA2_Channel5, 2);
					
			memcpy(G_u8Usart1SendBuf, msg, 10);
					
			USART_DMA_SendStart(DMA2_Channel5, 10);
		}

	    //DealWith_Data(pfifo); //处理数据
    } 
 }

        以下是串口中断函数,接收串口数据,当发现是完整的帧时,就调用OSMboxPost(Com4_MBOX,(void *)&msg);发送一个邮箱消息,进而那边的串口任务从挂起到唤醒,执行相应的过程。

使用ringbuffer实现任意数据类型的FIFO处理接收数据,可以参考:stm32f0串口 DMA 空闲中断接收——基于HAL库(代码篇)_噗噗bug博客-CSDN博客

void UART4_IRQHandler(void)
{
  uint16_t t;
	unsigned int i;
	unsigned char msg[50];
	OS_CPU_SR  cpu_sr;
	
	OS_ENTER_CRITICAL()     //保存全局中断标志,关总中断/
	OSIntNesting++;
	
	OS_EXIT_CRITICAL();  //恢复全局中断标志
	
	if(USART_GetITStatus(UART4,USART_IT_IDLE) == SET)          //检查中断是否发生
	{	
		RS485_TX_EN = 0;   
		
		DMA_Cmd(DMA2_Channel3,DISABLE);                         //关闭DMA传输
        DMA_ClearFlag( DMA2_FLAG_TC3 );  
		t = DMA_GetCurrDataCounter(DMA2_Channel3);              //获取剩余数量
		
		//FIFO_Add(pfifo, G_u8Usart1RecvBuf, UART4_RECV_MAXLEN - t); //fifo数据保存
		
		memcpy(msg, G_u8Usart1RecvBuf, UART4_RECV_MAXLEN - t);
	    OSMboxPost(Com4_MBOX,(void *)&msg);
		
		DMA_SetCurrDataCounter(DMA2_Channel3,UART4_RECV_MAXLEN);   //重新设置传输的数量
		
		 
		DMA_Cmd(DMA2_Channel3,ENABLE);                      //开启DMA传输
		USART_ReceiveData(UART4);                           //读一次数据,不然会一直进中断
		USART_ClearFlag(UART4,USART_FLAG_IDLE);             //清除串口中断标志
	}
	
	OSIntExit();
}

4.3 硬件初始化部分 

void BSP_Init(void)
{
 
  /* NVIC configuration */
   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
   RS485_Config();
   uart_init(9600);
   delay_init();       //延迟函数初始化
}

 4.4 RS485发送函数

注意:在485芯片的通信中,尤其要注意对485控制端DE的软件编程。为了可靠工作,在485总线状态切换时需要做适当延时,再进行数据收发。具体的做法是

     在数据发送状态下,   先将控制端置“1”,延时1ms左右的时间,在发送有效的数据,一包数据发送结束后再延时1ms后,将控制端置“0”,这样处理会使总线在状态切换时,有一个稳定的工作过程。代码中延迟10ms(参考:https://blog.csdn.net/yx_l128125/article/details/7914102)

#define RS485_TX_EN  PAout(15)   设置RS485 mode控制, RX:0, TX:1

void USART_DMA_SendStart(DMA_Channel_TypeDef *DMA_Streamx, u16 m_u16SendCnt)  
{    
	USART_DMACmd(UART4, USART_DMAReq_Tx, ENABLE);
    RS485_TX_EN = 1;
	delay_ms(10);	//延迟
    DMA_Cmd(DMA_Streamx, DISABLE);   
	delay_ms(10);	//延迟	
    DMA_SetCurrDataCounter(DMA_Streamx, m_u16SendCnt);  
    DMA_Cmd(DMA_Streamx, ENABLE);                      

	while(1)
	{
		if(DMA_GetFlagStatus(DMA2_FLAG_TC5)!=RESET)//µÈ´ýͨµÀ5´«ÊäÍê³É
		{
			DMA_ClearFlag(DMA2_FLAG_TC5);//Çå³ýͨµÀ5´«ÊäÍê³É±êÖ¾
			break; 
		 }
	}		
		
	delay_ms(10);	//延迟
	RS485_TX_EN=0;   
} 

4.5 主函数

int main(void)
{	
    unsigned  char os_err;
	
	OSInit();  
    //硬件初始化
    BSP_Init();    
  
   // FIFO 环型处理数据初始化
	pfifo = &fifo;
	FIFO_Init(pfifo, aRxFIFOBuffer, sizeof(uint8_t), RXFIFOBUFFERSIZE);
	
    OSInit();
	
    //先发送一段数据,可屏蔽
	for(i = 0; i < 50; i++)
	{
		G_u8Usart1SendBuf[i] = 0x10 + i;
	}
	USART_DMA_SendStart(DMA2_Channel5, 50);
	
	 os_err = OSTaskCreate((void (*) (void *)) App_TaskStart, (void *) 0,                    //指向任务代码的指针                         (void *) 0,                                           //任务开始执行时,传递给任务的参数的指针
               (OS_STK *) &App_TaskStartStk[APP_TASK_START_STK_SIZE - 1],    //分配给任务的堆栈的栈顶指针   从顶向下递减
               (INT8U) APP_TASK_START_PRIO);    //分配给任务的优先级 
	
    OSTimeSet(0);
    OSStart();      /* Start multitasking*/
}

五、运行串口调试

基于UCOSII的RS485通信(STM32F107)_第5张图片

 参考:[stm32][ucos] 1、基于ucos操作系统的LED闪烁、串口通信简单例程 - beautifulzzzz - 博客园

stm32f0串口 DMA 空闲中断接收——基于HAL库(代码篇)_噗噗bug博客-CSDN博客

代码:

https://download.csdn.net/download/qq_41070511/24419255

你可能感兴趣的:(嵌入式,stm32,嵌入式硬件,单片机,arm)