37 Linux 485收发切换延时导致接收数据错乱问题解决
37.1 前言这几天在对接Linux上位机与stm32开发板,通过485进行对接。Linux上位机端485是通过串口+485转换芯片+一个IO控制方向组成的。原本以为485这东西简简单单(之前有做过两块单片机的485驱动),没想到因为Linux系统调用延时导致485收发切换延时,导致Linux上位机始终不能正确接收stm32下位机回复的数据。期间以为是硬件问题,然后一直找硬件的人帮忙查...
37.1 前言
这几天在对接Linux上位机与stm32开发板,通过485进行对接。Linux上位机端485是通过串口+485转换芯片+一个IO控制方向组成的。原本以为485这东西简简单单(之前有做过两块单片机的485驱动),没想到因为Linux系统调用延时导致485收发切换延时,导致Linux上位机始终不能正确接收stm32下位机回复的数据。
期间以为是硬件问题,然后一直找硬件的人帮忙查看485阻容匹配是否正确,后面硬件的确发现一些问题,并作了一些改进,但仍然未能解决收发数据错乱问题,没办法,拿示波器看了又看,最后发现原来Linux上位机这边485发送数据后没能快速切换为接收状态,而下位机却在切换之前回复了上位机,因此导致Linux 485接收数据乱码。
37.2 切换延时探析
从波形找出了问题所在,回归串口编程,如下图示。在发送数据前将485芯片控为发送状态,发送后等待串口数据发送完成,然后在将485芯片控为接收状态。如果发送完不调用tcdrain而直接控IO可能导致数据没有发送完成就中断了,因此必须要找个方式等待数据发送完成。直接sleep肯定不行,因为sleep函数本来就是不精准的,有时sleep少,有时sleep多,难搞。
串口硬件发送数据应该很快不用那么25毫秒才把几个字节的数据发送出去啊!!什么问题呢?查阅资料是发现一个合理的解释是:Linux对硬件实时性高,对于用户请求的实时性较低。
这个得说说Linux工作队列相关机制,对于硬件操作Linux处理的很及时,但是对于数据Linux可能将其交给系统的下半部的内核线程去处理,这就可能导致用户的系统调用存在一定的延时。为此,如果想要解决这种影响,那么就换一种方式去等待数据发送完成而切换485的收发状态。
37.3 Linux 485收发切换延时解决方案选择
关于Linux485切换延时问题,成熟的解决方案有以下几种:
第一、硬件自控收发,要重新设计硬件不太现实,特别是对于已经发货很多的产品;
第二、更改串口驱动,使其增加对485 的支持,工作量太大,需要对tty子系统比较熟悉,难度有点大;
第三、编写Linux驱动模块,监控串口状态寄存器,有用户层触发监控再由内核线程自行判断控制方向,该方式实测可行,较第二更为简单。
本篇亦是基于第三种方式,解决Linux485切换延时的问题的。
37.4 Linux 485切换方案实现
目标:编写一个内核模块,模块内开一个内核线程(平时处于休眠状态),结合高精度定时器及条件变量查询串口状态寄存器的发送位标记,直到寄存器发送状态为置为空闲,此时将485切换为接收状态。该内核模块主要实现了iotcl函数,提供接口给用户层触发查询串口状态标志位情况,还有两个高精度内核定时器,用以精准延时或查询。
实现代码:
readStatus.h:
#ifndef _READSTATUS_H_
#define _READSTATUS_H_
struct stDataInfo
{
int nOption;
};
#define READSTATUS_IOC_MAGIC 0XF157
#define READSTATUS_IOCSET _IOW(READSTATUS_IOC_MAGIC, 0, struct stDataInfo)
#define READSTATUS_IOCGET _IOR(READSTATUS_IOC_MAGIC, 1, struct stDataInfo)
#endif
readStatus.c:
/*****************************************************************
* 包含头文件
******************************************************************/
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/hrtimer.h>
#include <linux/jiffies.h>
#include <linux/device.h> //class_createnikey
#include <linux/sched.h> //wake_up_process()
#include <linux/kthread.h> //kthread_create()、kthread_run()
#include <asm/uaccess.h>
#include <linux/err.h>
#include <linux/time.h>
#include <linux/delay.h> //for msleep
#include "readStatus.h"
/*****************************************************************
* 宏定义(仅在当前C文件使用的宏定义写在当前C文件中,否则需写在H文件中)
******************************************************************/
#define I_DEVICE_NAME "iTestDev"
#define I_UART0_REGFR 0x120a0018 //Uart状态寄存器地址
#define I_GPIO0_CTLREG 0x112F0034 //控制寄存器地址
#define I_GPIO0_DIRREG 0x120D0400 //方向寄存器地址
#define I_GPIO0_DATAREG 0x120D03FC //数据寄存器地址
/*****************************************************************
* 结构定义(仅在当前C文件使用的结构体写在当前C文件中,否则需写在H文件中)
******************************************************************/
struct _stMngrInfo{
int major;
struct class *iTest_class;
struct device *iTest_device;
unsigned int *pUartFR; /*串口状态寄存器指针*/
unsigned int *p485CtlReg; /*GPIO0_6控制寄存器指针*/
unsigned int *p485DirReg; /*GPIO0_6方向寄存器指针*/
unsigned int *p485DataReg; /*GPIO0_6数据寄存器指针*/
int nKPthreadWorkFlag; /*内核线程运行标记*/
struct task_struct *pkThreadTask; /*内核线程指针*/
int nQueWaiting; /*等待队列条件*/
wait_queue_head_t iwaitQue; /*等待队列*/
struct hrtimer ihrtimer; /*高精度定时器*/
int SSwitchDelayFlag; /*发送后接收延时*/
struct hrtimer Sihrtimer; /*高精度定时器*/
};
/*****************************************************************
* 全局变量定义
******************************************************************/
struct _stMngrInfo gstMngrInfo;
static DEFINE_MUTEX(gReadStatusMutex); /*互斥锁*/
/*****************************************************************
* 静态变量定义
******************************************************************/
/*****************************************************************
* 外部变量声明(如果全局变量没有在其它的H文件声明,引用时需在此处声明,
*如果已在其它H文件声明,则只需包含此H文件即可)
******************************************************************/
static int iTest_HwInit(struct _stMngrInfo *pgstMngrInfo)
{
pgstMngrInfo->pUartFR = ioremap(I_UART0_REGFR, 4);
if(pgstMngrInfo->pUartFR==NULL){
printk(KERN_EMERG "ioremap %x faild\n",I_UART0_REGFR);
return -1;
}
pgstMngrInfo->p485CtlReg = ioremap(I_GPIO0_CTLREG, 4);
if(pgstMngrInfo->p485CtlReg==NULL){
printk(KERN_EMERG "ioremap %x faild\n",I_GPIO0_CTLREG);
goto iExit1;
}
pgstMngrInfo->p485DirReg = ioremap(I_GPIO0_DIRREG, 4);
if(pgstMngrInfo->p485DirReg==NULL){
printk(KERN_EMERG "ioremap %x faild\n",I_GPIO0_DIRREG);
goto iExit2;
}
pgstMngrInfo->p485DataReg = ioremap(I_GPIO0_DATAREG, 4);
if(pgstMngrInfo->p485DataReg==NULL){
printk(KERN_EMERG "ioremap %x faild\n",I_GPIO0_DATAREG);
goto iExit3;
}
printk(KERN_EMERG "iTest_HwInit OK.\n");
return 0;
iExit3:
iounmap(pgstMngrInfo->p485DataReg);
iExit2:
iounmap(pgstMngrInfo->p485CtlReg);
iExit1:
iounmap(pgstMngrInfo->pUartFR);
printk(KERN_EMERG "iTest_HwInit Fail.\n");
return -1;
}
static unsigned int iTest_Set485Dir(int nOption)
{
struct _stMngrInfo *pgstMngrInfo = &gstMngrInfo;
unsigned int nReg485Value = 0;
//选为GPIO6模式
nReg485Value = readl(pgstMngrInfo->p485CtlReg);
nReg485Value &= ~0xF;
writel(nReg485Value,pgstMngrInfo->p485CtlReg);
//设为输出
nReg485Value = readb(pgstMngrInfo->p485DirReg);
nReg485Value |= (1<<6);
writeb((char)nReg485Value,pgstMngrInfo->p485DirReg);
//设置输出电平
if(nOption==1)
{
nReg485Value = readb(pgstMngrInfo->p485DataReg);
nReg485Value |= (1<<6);
writeb((char)nReg485Value,pgstMngrInfo->p485DataReg);
}
else
{
nReg485Value = readb(pgstMngrInfo->p485DataReg);
nReg485Value &= ~(1<<6);
writeb((char)nReg485Value,pgstMngrInfo->p485DataReg);
}
return 0;
}
static int iTest_wakeUpKThread(void)
{
struct _stMngrInfo *pgstMngrInfo = &gstMngrInfo;
mutex_lock(&gReadStatusMutex);
pgstMngrInfo->nQueWaiting = 1;
mutex_unlock(&gReadStatusMutex);
wake_up(&pgstMngrInfo->iwaitQue);
return 0;
}
static unsigned short iTest_scanUARTFRBusyBit(struct _stMngrInfo *pgstMngrInfo)
{
unsigned short sRegUartValue = 0;
sRegUartValue = readw(pgstMngrInfo->pUartFR);
sRegUartValue = (sRegUartValue &(1<<3));
return sRegUartValue;
}
static enum hrtimer_restart iTest_ShrtimerHander(struct hrtimer *timer)
{
iTest_Set485Dir(0);//设为接收状态
return HRTIMER_NORESTART;
}
static enum hrtimer_restart iTest_hrtimerHander(struct hrtimer *timer)
{
mutex_lock(&gReadStatusMutex);
gstMngrInfo.nQueWaiting = 1;
mutex_unlock(&gReadStatusMutex);
wake_up(&gstMngrInfo.iwaitQue);
return HRTIMER_NORESTART;
}
static int iTest_kthread(void *arg)
{
int nRet = 0;
struct _stMngrInfo *pgstMngrInfo = (struct _stMngrInfo *)arg;
while(!kthread_should_stop())
{
/*添加这个是为了能退出线程*/
if(pgstMngrInfo->nKPthreadWorkFlag == 0)
{
msleep(1000);
continue;
}
/*等待唤醒,然后查看串口发送位状态*/
wait_event(pgstMngrInfo->iwaitQue,pgstMngrInfo->nQueWaiting);
mutex_lock(&gReadStatusMutex);
pgstMngrInfo->nQueWaiting = 0;
mutex_unlock(&gReadStatusMutex);
nRet = iTest_scanUARTFRBusyBit(pgstMngrInfo);
if(nRet==0)
{
hrtimer_start(&pgstMngrInfo->Sihrtimer,ktime_set(0,5000*1000),HRTIMER_MODE_REL);
}
else
{
/*启动高精度定时器,1ms后唤醒线程*/
hrtimer_start(&pgstMngrInfo->ihrtimer,ktime_set(0,1000*1000),HRTIMER_MODE_REL);
}
}
printk(KERN_EMERG "iTest_kthread exit OK!\n");
return 0;
}
static long iTest_unioctl(struct file *pfilp, unsigned int cmd, unsigned long arg)
{
int nRet = 0;
struct stDataInfo iDataInfo;
if (copy_from_user(&iDataInfo, (const void *)arg, sizeof(struct stDataInfo))) {
nRet = -EFAULT;
goto iExit;
}
switch (cmd) {
case READSTATUS_IOCGET: //get 内核态数据
{
if (copy_to_user((void *)arg, &iDataInfo, sizeof(struct stDataInfo))) {
nRet = -EFAULT;
}
}
break;
case READSTATUS_IOCSET:
{
if(iDataInfo.nOption == 1)
{
//设为发送
iTest_Set485Dir(1);
}
else if(iDataInfo.nOption == 0)
{
//唤醒线程
iTest_wakeUpKThread();
}
}
break;
default:
{
pr_err("wrong ioctl cmd[%d]\n", cmd);
nRet = -EINVAL;
}
break;
}
iExit:
return nRet;
}
static const struct file_operations iTest_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = iTest_unioctl,
};
static int __init iTest_Init(void)
{
int nRet = -1;
memset(&gstMngrInfo,0,sizeof(gstMngrInfo));
/* 主设备号设置为0表示由系统自动分配主设备号 */
gstMngrInfo.major = register_chrdev(0, I_DEVICE_NAME, &iTest_fops);
/* 创建iTest_class类 */
gstMngrInfo.iTest_class = class_create(THIS_MODULE, "iTestClass");
/* 在iTest_class类下创建设备,并在/dev/目录下创建iTestDevice节点*/
gstMngrInfo.iTest_device = device_create(gstMngrInfo.iTest_class, NULL, MKDEV(gstMngrInfo.major, 0), NULL, "iTestDevice");
nRet = iTest_HwInit(&gstMngrInfo);
if(nRet<0)
return -1;
/*高精度定时器初始化*/
/*初始化等待队列*/
gstMngrInfo.nQueWaiting = 0;
init_waitqueue_head(&gstMngrInfo.iwaitQue);
/*初始化高精度定时器*/
hrtimer_init(&gstMngrInfo.ihrtimer,CLOCK_MONOTONIC,HRTIMER_MODE_REL);
gstMngrInfo.ihrtimer.function = iTest_hrtimerHander; /*唤醒查询线程回调函数*/
hrtimer_init(&gstMngrInfo.Sihrtimer,CLOCK_MONOTONIC,HRTIMER_MODE_REL);
gstMngrInfo.Sihrtimer.function = iTest_ShrtimerHander; /*切换接收回调*/
/*创建内核线程*/
gstMngrInfo.nKPthreadWorkFlag = 1;
gstMngrInfo.pkThreadTask = kthread_create(iTest_kthread,&gstMngrInfo,"iTest_task");
if (IS_ERR(gstMngrInfo.pkThreadTask))
{
nRet = PTR_ERR(gstMngrInfo.pkThreadTask);
printk(KERN_EMERG "kthread_create fail.\n");
return nRet;
}
//开始线程
wake_up_process(gstMngrInfo.pkThreadTask);
printk(KERN_EMERG "iTest_Init OK.\n");
return nRet;
}
static void __exit iTest_Exit(void)
{
if (gstMngrInfo.pkThreadTask!=NULL)
{
gstMngrInfo.nKPthreadWorkFlag = 0;
iTest_wakeUpKThread();
kthread_stop(gstMngrInfo.pkThreadTask);
gstMngrInfo.pkThreadTask = NULL;
}
hrtimer_cancel(&gstMngrInfo.ihrtimer);
hrtimer_cancel(&gstMngrInfo.Sihrtimer);
iounmap(gstMngrInfo.p485DataReg);
iounmap(gstMngrInfo.p485CtlReg);
iounmap(gstMngrInfo.pUartFR);
unregister_chrdev(gstMngrInfo.major, I_DEVICE_NAME);
device_unregister(gstMngrInfo.iTest_device);
class_destroy(gstMngrInfo.iTest_class);
printk(KERN_EMERG "iTest_Exit OK.\n");
}
module_init(iTest_Init);
module_exit(iTest_Exit);
MODULE_AUTHOR("ljc");
MODULE_LICENSE("GPL");
更多推荐
所有评论(0)