一、实现效果
基于ucosii实时操作系统的RS485通信,采用USART + DMA进行收发,
二、开发环境
- 开发工具:KEIL V5
- 开发板: STM32f107RC
- 采用方式:USART + DMA
- 使用系统:UCOSII
三、RS485部分原理
在RS-485通讯网络中,节点中的串口控制器使用RX与TX信号线连接到收发器上,而收发器通过差分线连接到网络总线,串口控制器与收发器之间一般使用TTL信号传输,收发器与总线则使用差分信号来传输。
发送数据时,串口控制器的TX信号经过收发器转换成差分信号传输到总线上,
而接收数据时,收发器把总线上的差分信号转化成TTL信号通过RX引脚传输到串口控制器中。
MCU管脚输出TTL电平,TTL电平的意思是,当MCU管脚输出0电平时,一般情况下电压是0V,当MCU管脚输出1电平时,电压是5V。因TTL电平的是由一条信号线,一条地线产生,信号线上的干扰信号会跟随有效信号传送到接收端,使得有效信号受到干扰,485通讯实际上是把MCU出来的TTL电平通过硬件层的一个转换器芯片进行转换
RS-485通讯网络的最大传输距离可达1200米,总线上可挂载128个通讯节点,而由于RS-485网络只有一对差分信号线,它使用差分信号来表达逻辑,当AB两线间的电压差为-6V~-2V时表示逻辑1,当电压差为+2V~+6V表示逻辑0,在同一时刻只能表达一个信号,所以它的通讯是半双工形式的。
在单个实验板中,作为串口控制器的STM32从USART外设引出TX和RX两个引脚与RS-485收发器MAX485相连,收发器使用它的A和B引脚连接到RS-485总线网络中。为了方便使用,我们每个实验板引出的A和B之间都连接了1个120欧的电阻作为RS-485总线的端电阻,所以要注意如果要把实验板作为一个普通节点连接到现有的RS-485总线时,是不应添加该电阻的!
MAX485芯片中有"RE"和"DE"两个引脚,用于控制485芯片的收发工作状态的,当RE引脚为低电平时,485芯片处于接收状态,当DE引脚为高电平时芯片处于发送状态。实验板中使用了STM32的PD11直接连接到这两个引脚上,所以通过控制PD11的输出电平即可控制485的收发状态。
实验板之间A与A连接,B与B连接即可。
四、配置操作
建立了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*/
}
五、运行串口调试
参考:[stm32][ucos] 1、基于ucos操作系统的LED闪烁、串口通信简单例程 - beautifulzzzz - 博客园
stm32f0串口 DMA 空闲中断接收——基于HAL库(代码篇)_噗噗bug博客-CSDN博客