概述:

1.了解Lora技术的基本知识

2.了解通信协议用途

3.掌握Lora模块SPI配置方法

4.掌握简单的Lora模块数据对传方法

5.掌握Lora通讯协议使用方法

一、什么是LoRa

LoRa(Long Range Radio,远距离无线电)是一种基于扩频技术的远距离无线传输技术,是LPWAN通信技术中的一种,是Semtech公司创建的低功耗局域网无线标准。这一方案为用户提供一种简单的能实现远距离、低功耗无线通信的手段。它最大的特点就是在同样的功耗条件下比其他无线方式传播的距离更远,实现了低功耗和远距离的统一,它在同样的功耗下比传统的无线射频通信距离扩大3~5倍。目前,LoRa主要在ISM频段运行,主要包括433MHz、868MHz、915MHz等。
2.LoRa的特性
传输距离:城镇中可达2~5km,郊区可达15km;
工作频率:ISM 频段,包括433MHz、868MHz、915MHz等;
标准:IEEE 802.15.4g;
调制方式:基于扩频技术,是线性调制扩频(CSS)的一个变种,具有前向纠错(FEC)能力,是Semtech公司私有专利技术;
容量:一个LoRa网关可以连接成千上万个LoRa节点;
电池寿命:长达10年;
安全:AES128加密;
传输速率:几十到几百kbit/s,速率越低传输距离越长。

二、Lora和LoraWAN

Lora是LoraWAN的一个子集,Lora仅包括物理层定义,LoraWAN包括链路层定义。其实LoraWAN不是完整的通通信协议,因为他只定义了物理层和链路层,网络层和传输层没有,功能也并不完善,也没有漫游,没有组网管理等通信协议重要功能。

三、Lora模块

采用的LSD4RF-2F717N30是LoRa SX1278 470M100mW标准模块,是基于Semtech射频集成芯片SX127X的射频模块,是一款高性能物联网无线收发器。

1.SX1276/77/78收发器

SX1276/77/78是137~1020MHz低功耗远距离收发器,主要采用LoRa远程调制解调器,用于超长距离扩频通信,抗干扰性强,能够最大限度地降低电流消耗。
借助Semtech的LoRa专利调制技术,SX1276/77/78采用低成本的品体和物料即可获得超过-148dBm的高灵敏度。此外,高灵敏度与20dBm功率放大器的集成便这些器件的链路预算达到了行业领先水平,成为远距离传输和对可靠性要求极高的应用的最佳选择。相较传统调制技术,LoRa调制技术在抗阻塞和选择性方面也具有明显优势,解决了传统设计方案无法同时兼顾距离、抗干扰和功耗的问题。
这些器件还支持WM-BusIIEEE 802.15.4g等系统的高性能(G)FSK模式。与同类器件相比,SX1276/77/78在大幅降低电流消耗的基础上,还显著优化了相位噪声、选择性、接收机线性度、三阶输人截取点(IIP3)等各项性能。
SX1276的带宽范围为7.8~500kHz,扩频因子为6~12,并覆盖所有可用频段。SX1277的带宽和频段范围与SX1276相同,但扩频因子为6~9。SX1278的带宽和扩频因子选择与SX1276相同,但仅覆盖UHF频段。
(1)关键产品特性
LoRa调制解调器;
最大链路预算可达168dB;
20dBm-100mW电压变化时恒定的射频功率输出;
14dBm的高效率功率放大器;
可编程比特率高达300kbit/s;
高灵敏度:低至-148dBm;
高可靠性的前端:IIP3=-11dBm;
卓越的抗阻塞特性;
9.9mA 低接收电流,200nA寄存器保持电流;
分辨率为61Hz、完全集成的频率合成器;
支持FSK、GFSK、MSK、GMSK、LoRa及OOK调制方式:
内置式位同步,用于时钟恢复;
前导码检测:
127dB的RSSI动态范围:
自动射频信号检测,CAD模式和超高速AFC;
带有CRC、高达256B的数据包引擎;
内置温度传感器和低电量指示器。
(2)应用

远程灌溉系统;
自动抄表;
家庭和楼宇自动化:
无线告警和安防系统;
工业监视与控制;

四、SPI

1.SPI简介

LoRa芯片与MCU通过SPI进行通信。SPI(Serial Peripheral Interface Bus)是由摩托罗拉公司开发的高速全双工同步串行通信协议。SPI支持一主多从,这点类似于IC,但是又与I2C选通从设备的方式不同,IC是通过发送从机地址来选通从机,而SPI是通过拉低连接到从机的NSS引脚对从机进行选通的。SPI一般应用由四个引脚组成(一主一从):
SCLK(Serial Clock):串行时钟,由主机发出;
MOSI(Master Output,Slave Input):主机输出从机输入信号,由主机发出;
MISO(Master Input,Slave Output):主机输入从机输出信号,由从机发出:NSS:选择信号,由主机发出,一般是低电位有效。

2. SPI代码配置

(1)引脚初始化

通过SpiInit()来实现,这个函数在BoardDisableIrq( )中调用。

void SpiInit( Spi_t *obj, PinNames mosi, PinNames miso, PinNames sclk, PinNames nss )参数说明:Spi_t *obj:指向待初始化SPI结构体;PinNames mosi:主机输出从机输入引脚;PinNames miso:主机输入从机输出引脚;PinNames sclk:串行时钟引脚;PinNames nss:片选引脚;

void SpiInit( Spi_t *obj, PinNames mosi, PinNames miso, PinNames sclk, PinNames nss )
{BoardDisableIrq( );//禁止中断// Choose SPI interface according to the given pinsif( mosi == PA_7 ){__HAL_RCC_SPI1_FORCE_RESET( );__HAL_RCC_SPI1_RELEASE_RESET( );__HAL_RCC_SPI1_CLK_ENABLE( );obj->Spi.Instance = ( SPI_TypeDef* )SPI1_BASE;//建立SPI,也就是obj就是SPI1//将GPIO口初始化GpioInit( &obj->Mosi, mosi, PIN_ALTERNATE_FCT, PIN_PUSH_PULL, PIN_PULL_DOWN, GPIO_AF5_SPI1 );GpioInit( &obj->Miso, miso, PIN_ALTERNATE_FCT, PIN_PUSH_PULL, PIN_PULL_DOWN, GPIO_AF5_SPI1 );GpioInit( &obj->Sclk, sclk, PIN_ALTERNATE_FCT, PIN_PUSH_PULL, PIN_PULL_DOWN, GPIO_AF5_SPI1 );GpioInit( &obj->Nss, nss, PIN_ALTERNATE_FCT, PIN_PUSH_PULL, PIN_PULL_UP, GPIO_AF5_SPI1 );if( nss == NC ){obj->Spi.Init.NSS = SPI_NSS_SOFT;//NSS片选信号由软件单独控制SpiFormat( obj, SPI_DATASIZE_8BIT, SPI_POLARITY_LOW, SPI_PHASE_1EDGE, 0 );//设置SPI通讯方式,配置为主机模式}else{SpiFormat( obj, SPI_DATASIZE_8BIT, SPI_POLARITY_LOW, SPI_PHASE_1EDGE, 1 );//设置SPI通讯方式,配置为从机模式}}else if( mosi == PB_15 ){//初始化SPI2__HAL_RCC_SPI2_FORCE_RESET( );__HAL_RCC_SPI2_RELEASE_RESET( );__HAL_RCC_SPI2_CLK_ENABLE( );obj->Spi.Instance = ( SPI_TypeDef* )SPI2_BASE;//建立SPI obj也就是SPI2//将GPIO口初始化GpioInit( &obj->Mosi, mosi, PIN_ALTERNATE_FCT, PIN_PUSH_PULL, PIN_PULL_DOWN, GPIO_AF5_SPI2 );GpioInit( &obj->Miso, miso, PIN_ALTERNATE_FCT, PIN_PUSH_PULL, PIN_PULL_DOWN, GPIO_AF5_SPI2 );GpioInit( &obj->Sclk, sclk, PIN_ALTERNATE_FCT, PIN_PUSH_PULL, PIN_PULL_DOWN, GPIO_AF5_SPI2 );GpioInit( &obj->Nss, nss, PIN_ALTERNATE_FCT, PIN_PUSH_PULL, PIN_PULL_UP, GPIO_AF5_SPI2 );if( nss == NC ){obj->Spi.Init.NSS = SPI_NSS_SOFT;//NSS片选信号由软件单独控制SpiFormat( obj, SPI_DATASIZE_8BIT, SPI_POLARITY_LOW, SPI_PHASE_1EDGE, 0 );//设置SPI通讯方式,配置为主机模式}else{SpiFormat( obj, SPI_DATASIZE_8BIT, SPI_POLARITY_LOW, SPI_PHASE_1EDGE, 1 );//设置SPI通讯方式,配置为从机模式}}SpiFrequency( obj, 10000000 );//设置SPI速度HAL_SPI_Init( &obj->Spi );//生效BoardEnableIrq( );//使能中断
}

(2)设置SPI通讯方式

SpiFormat( Spi_t *obj, int8_t bits, int8_t cpol, int8_t cpha, int8_t slave )中各参数如下:

Spi_t *obj:SPI结构体;

int8_t bits:帧格式,选择8位

int8_t cpol:设置时钟极性。这里是低电平

int8_t slave:主从模式,0主,1从

(3)Lora调制解调

(1)频率:频率建议在433MHZ附近,也430,431,432,用户根据设置频率确定合适的信道。

(2)发射功率:Lora发射功率由参数TX_OUTPUT_POWER;这个值越大,传输的距离越远,最大值不超过20dBm。

(3)Lora数据包结构:

3.编写关键函数 NS_Radio.c

void NS_RadioEventsInit( void )//射频初始化函数
{// Radio initializationRadioEvents.TxDone = OnTxDone;RadioEvents.RxDone = OnRxDone;RadioEvents.TxTimeout = OnTxTimeout;RadioEvents.RxTimeout = OnRxTimeout;RadioEvents.RxError = OnRxError;Radio.Init( &RadioEvents );
}void OnTxDone( void )//发送完成调用此函数
{Radio.Sleep( );Radio.Rx( RX_TIMEOUT_VALUE );
}void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr )//接收完成调用,读取数据、数据长度、信号强度、信噪比。
{Radio.Sleep( );LoRaBufferSize = size;memcpy( LoRaBuffer, payload, LoRaBufferSize );RssiValue = rssi;SnrValue = snr;
//    printf( "Rx=%s\r\nRssiValue=%d\r\nSnrValue=%d\r\n",LoRaBuffer,RssiValue,SnrValue );Radio.Rx( RX_TIMEOUT_VALUE );
}void OnTxTimeout( void )//发送超时调用
{Radio.Sleep( );Radio.Rx( RX_TIMEOUT_VALUE );
}void OnRxTimeout( void )//接收超时调用
{Radio.Sleep( );Radio.Rx( RX_TIMEOUT_VALUE );
}void OnRxError( void )//接收错误调用
{Radio.Sleep( );Radio.Rx( RX_TIMEOUT_VALUE );
}

项目实验

项目介绍:方圆5平方千米的植物园,粗放式管理,管委会要求对园区环境进行检测,温湿度光照等。要求:低成本,节约经费;先实现点对点,能够在上位机进行数据获取,后期升级位云平台获取。

(1)  关键接口函数解析:

void LoRa_Send( uint8_t *TxBuffer, uint8_t len )//TxBuffer是一个指针,指向用户需要发送的Lora无线数据首地址。{Radio.Send( TxBuffer, len);
}void MyRadioRxDoneProcess( void )//接收Lora无线数据,用户需要在函数中解析无线数据的功能代码或函数。{uint16_t BufferSize = 0;uint8_t RxBuffer[BUFFER_SIZE];BufferSize = ReadRadioRxBuffer( (uint8_t *)RxBuffer );if(BufferSize>0){//用户在此处添加接收数据处理功能的代码;printf("LoRa TempRh\r\n");hal_temHumInit();//初始化温湿度模块connectionreset();//重置温湿度模块IIC通信Tim3McuInit(1);//定时器初始化,设置定时中断1ms中断一次}
}void PlatformInit(void)//硬件平台初始化
{// 开发板平台初始化BoardInitMcu();BoardInitPeriph();// 开发板设备初始化OLED_Init();//液晶初始化USART1_Init(115200);//串口1初始化OLED_Clear();OLED_InitView();//OLED屏幕显示初始信息printf("新大陆教育 LoRa \r\n");//Lora模块初始化NS_RadioInit( (uint32_t) RF_PING_PONG_FREQUENCY, (int8_t) TX_OUTPUT_POWER, (uint32_t) TX_TIMEOUT_VALUE, (uint32_t) RX_TIMEOUT_VALUE );//请在下方添加用户初始化代码//IWDG_PrmInit(2048);//独立看门狗初始化,超时设置为2048ms
}//-----------------------------------------------------------
//根据通信协议制定响应命令结构
#define START_HEAD 0x55//帧头
#define CMD_READ   0x01//读数据
#define ACK_OK     0x00//响应OK
#define ACK_NONE   0x01//无数据
#define ACK_ERR    0x02//数据错误
//定义网络编号和设备地址
#define MY_NET_ID  0xD0C2       //网络ID
#define MY_ADDR    0x01         //设备地址/*全局变量*/
int8_t temperature = 25;    //温度,单位:℃
int8_t humidity = 60;       //湿度,单位:%//函数声明
void LoRa_Send( uint8_t *TxBuffer, uint8_t len );
void MyRadioRxDoneProcess( void );
void OLED_InitView(void);
void PlatformInit(void);uint8_t CheckSum(uint8_t *buf, uint8_t len)//计算校验和
{uint8_t temp = 0;while(len--){temp += *buf;buf++;}return (uint8_t)temp;
}/**********************************************************************************************
*函数:uint8_t *ExtractCmdframe(uint8_t *buf, uint8_t len, uint8_t CmdStart)
*功能:从一串数据中提取出命令帧出现起始地址
*输入:
*     uint8_t *buf,指向待提取的数据缓冲区地址
*     uint8_t len,缓冲区数据长度
*     uint8_t CmdStart,命令帧起始字节
*输出:无
*返回:返回首次出现命令帧的地址,若数据中无命令帧数据,则返回NULL
*特殊说明:无
**********************************************************************************************/
uint8_t *ExtractCmdframe(uint8_t *buf, uint8_t len, uint8_t CmdStart)
{uint8_t *point = NULL;uint8_t i;for(i=0; i<len; i++){if(CmdStart == *buf){point = buf;return point;}}return NULL;
}/**********************************************************************************************
*函数:uint16_t GetHexStr(uint8 *input, uint16_t len, uint8 *output)
*功能:生成数组的16进制形式的字符串格式,成员间以空格隔开
*     例如:由{0xA1,0xB2,0xC3}生成"A1 B2 C3"
*输入:uint8 *input-指向输入缓存区, uint16_t len-输入数据的字节长度
*输出:uint8 *output-指向输出缓存区
*返回:返回生成字符串的长度
*特殊说明:无
**********************************************************************************************/
uint16_t GetHexStr(uint8_t *input, uint16_t len, uint8_t *output)
{for(uint16_t i=0; i<len; i++){sprintf((char *)(output+i*3),"%02X ", *input);input++;}return strlen((const char *)output);
}

(2)请求命令参照数据通信结构,传感器接收网关读传感数据命令后,节点需要按照通信规约格式上报网关。请求命令解析数据代码如下:

//--------------------------------------------------------
//数据解析
void LoRa_DataParse( uint8_t *LoRaRxBuf, uint16_t len )
{uint8_t *DestData = NULL;
#define HEAD_DATA  *DestData     //帧头
#define CMD_DATA   *(DestData+1) //命令
#define NETH_DATA  *(DestData+2) //网络ID高字节
#define NETL_DATA  *(DestData+3) //网络ID低字节
#define ADDR_DATA  *(DestData+4) //地址  #define ACK_DATA        *(DestData+5) //响应
#define LEN_DATA        *(DestData+6) //长度
#define DATASTAR_DATA   *(DestData+7) //数据域起始DestData = ExtractCmdframe((uint8_t *)LoRaRxBuf, len, START_HEAD);//输入数据 长度 帧给DestDataif(DestData != NULL)//检索到数据帧头{if((DestData - LoRaRxBuf) > (len - 6)) return;//数据长度不足构成一帧完整数据if(CMD_DATA != CMD_READ) return;//命令错误if(CheckSum((uint8_t *)DestData, 5) != (*(DestData+5))) return;//校验不通过,仅适用于校验读数据命令的校验if(((((uint16_t)NETH_DATA)<<8)+NETL_DATA) != MY_NET_ID) return;//网络ID不一致//发送读响应if(ADDR_DATA != MY_ADDR) return;//地址不一致/--------------------------------------------------------------------
//根据通信协议发送数据又称响应uint8_t RspBuf[BUFFER_SIZE]= {0};memset(RspBuf, '\0', BUFFER_SIZE);RspBuf[0]=START_HEAD;//帧头 RspBuf[1]=CMD_READ;//命令 ,0x01读传感数据 RspBuf[2]=(uint8_t)(MY_NET_ID>>8);//网络ID低字节  RspBuf[3]=(uint8_t)MY_NET_ID;//网络ID高字节  RspBuf[4]=MY_ADDR;//地址 RspBuf[5]=ACK_OK;//响应OK 0x00 sprintf((char *)(RspBuf+7),"temperature(℃):%d|humidity(%%):%d", temperature, humidity);//数据域,sprintf中,两个“%”表示输出“%”。在采集传感函数中已经将采集值放入这两个变量中,发送即可。RspBuf[6]=strlen((const char *)(RspBuf+7))+1;//数据域长度RspBuf[6+RspBuf[6]]=CheckSum((uint8_t *)RspBuf, 6+RspBuf[6]);Radio.Send( RspBuf, 7+RspBuf[6]);//发送响应数据GpioToggle( &Led1 );//发送数据切换亮灯指示}
}

(3)主机接收代码如下:

/**********************************************************************************************
*函数:void MyRadioRxDoneProcess( void )
*功能:无线模块数据接收完成处理进程函数
*输入:无
*输出:无
*返回:无
*特殊说明:接收到的无线数据保存在RxBuffer中,BufferSize为接收到的无线数据长度
**********************************************************************************************/
void MyRadioRxDoneProcess( void )
{uint16_t BufferSize = 0;uint8_t RxBuffer[BUFFER_SIZE];BufferSize = ReadRadioRxBuffer( (uint8_t *)RxBuffer );if(BufferSize>0){//用户在此处添加接收数据处理功能的代码GpioToggle( &Led2 );//收到数据切换亮灯指示LoRa_DataParse( (uint8_t *)RxBuffer, BufferSize );//调用数据解析函数}
}

(4)主函数将硬件平台初始化,不断循环接收发送函数。

//-----------------------------获取传传感器数据------------------------
void LoRa_GetSensorDataProcess(void)
{const uint16_t time = 1000;if(User0Timer_MS > time) //1ms进中断User0Timer_MS ++;{User0Timer_MS = 0;uint16_t Temp, Rh;call_sht11((uint16_t *)(&Temp), (uint16_t *)(&Rh));//采集温湿度数据temperature = (int8_t)Temp;    //温度,单位:℃  将采集值传给前边定义的两个变量humidity = (int8_t)Rh;       //湿度,单位:%     将采集值传给前边定义的两个变量
//------------------oled显示--------------------------------------------char StrBuf[64]= {0};memset(StrBuf, '\0', 64);sprintf(StrBuf, " %d DegrCe",temperature);OLED_ShowString(0,4,(uint8_t *)StrBuf);//OLED显示当前温度memset(StrBuf, '\0', 64);sprintf(StrBuf, " %d %%",humidity);OLED_ShowString(0,6,(uint8_t *)StrBuf);//OLED显示当前相对湿度}
}int main( void )
{PlatformInit();while( 1 ){//IWDG_PrmRefresh( );//喂独立看门狗MyRadioRxDoneProcess();//LoRa无线射频接收数据处理进程LoRa_GetSensorDataProcess();}
}

Lora通信应用开发相关推荐

  1. LoRa SX1278通信代码开发学习

    前言 最近在学习和摸索LoRa SX1278无线发射模块,其中学到了很多新知识和对SX1278也有了深一点的认识,现在将学习开发中遇到的问题.解决方法.调试完成和低功耗等内容分享出来,也是一种学习记录 ...

  2. 树莓派移植SX1278 LoRa通信--使用wiringPiSPI移植SPI通信接口

    一.SPI接口 树莓派3B+上的SPI接口如下所示,有两组SPI,分别由CE0和CE1来进行选择. 首先查看树莓派的SPI是否启用,在/dev查看是否有spidev0.0和spidev0.1 如果不存 ...

  3. 基于无线lora通信实现远程采集输出0~10v,0~20ma模拟量信号以及开关量信号

    深圳市综科智控科技开发有限公司是一家专注于生产与研发工业智能自动化设备及软件系统.工业物联网设备及软件系统的高新技术企业. 公司致力于为客户提供从前端数据采集.传感器接入.IO控制.通信组网到云端联网 ...

  4. LoRa学习:LoRa通信调制解调的实现原理与性能

    更多技术干货,欢迎扫码关注博主微信公众号:HowieXue,一起学习探讨软硬件技术知识经验,关注就有海量学习资料免费领哦: LoRa学习:LoRa调制解调原理与性能 目录 LoRa学习:LoRa调制解 ...

  5. arduino lora通讯_智能酒桶-arduino+lora通信

    购买的模块终于到齐了,来个接线图: 第一步:通过官方工具,将lora模块进行初始化,注意一定要选定点传输方式,我一开始不知道在这里搞了好久.第一个模块设置地址为1,这个模块到时接树莓派,第二个模块设置 ...

  6. 通信工程专业就业之------通信协议栈开发(LTE/NR)

    通信工程专业-信息与通信工程学科的学生,就业可以选择做通信协议栈开发,在校期间大致要学习: 通信知识:信号与系统,数字信号处理,通信原理,移动通信原理,电路(数字,模拟,射频),电磁场与电磁波 编程知 ...

  7. Windows蓝牙通信的开发

    周四接到关于window上的蓝牙开发项目,预定时间在五天之内结束,但是五天的时间很快过去,还是没有做出来,只能搜到蓝牙设备,并且可以本地的蓝牙设备和远程的蓝牙设备,所以现在还在持续的更新中,所以用博客 ...

  8. arduino lora通讯_Arduino开发板和树莓派之间实现Lora点对点通讯

    随着物联网.互联汽车.M2M.工业4.0等的出现,LoRa越来越受欢迎.由于其能够以非常低的功率进行长距离通信,因此它非常适合设计人员用于发送/接收来自电池供电的数据.我们已经讨论了LoRa的基础知识 ...

  9. Android蓝牙通信功能开发

    1. 概述 Bluetooth 是几乎现在每部手机标准配备的功能,多用于耳机 mic 等设备与手机的连接,除此之外,还可以多部手机之间建立 bluetooth 通信,本文就通过 SDK 中带的一个聊天 ...

最新文章

  1. 如何把Windows安装的所有打印机列出来
  2. docker commit 发布自己的镜像
  3. 用c语言编写图书成绩管理系统,学生成绩管理系统(c语言编写).doc
  4. SpringBoot+POI实现导入Excel时验证并返回错误Cell标红的文件
  5. 访问进程环境变量environ时的一个坑
  6. [转贴] 软件测试职业发展的 A 面和 B 面
  7. C++描述杭电OJ 2012. 素数判定 ||
  8. burpsuite破解版
  9. 把表单转成json,并且name为key,value为值
  10. 责任链模式(Chain of Responsibility Pattern)
  11. 计算机excel公式教案,Excel利用函数进行数据计算(教案)
  12. 数据库技术与应用课程设计-学生信息管理系统
  13. 浅析ARM公司在物联网领域的战略布局
  14. 将代码提交到github上
  15. 在网格的边缘试探——企业服务行业如何试水 Istio
  16. 采样定理与奈奎斯特极限
  17. 数字信号处理——DDS模块设计(3)
  18. 关于宽带拨号上网的笑话,我自己闹的笑话。
  19. FigDraw 13. SCI 文章绘图之桑葚图及文章复现(Sankey)
  20. springboot下载依赖包

热门文章

  1. Android WebView加载时出现闪烁
  2. 基于Atmega16的人机交互实验
  3. 单基因GSEA,还是基于单基因表达谱分组后的GSEA?
  4. docker容器启动失败(Error response from daemon: error creating overlay mount to /app/docker/overlay2/)
  5. _THROW 何解?
  6. 电脑维修入门:电脑故障排除的十种方法!
  7. PMP备考指南之第十三章:项目干系人管理
  8. npm install 报错 ERR! gyp ERR! cwd D:\workspace\node_modules\node-sass错误解决/npm
  9. 2017年8月11日草原天路沽源太仆寺旗3日游
  10. 公立医院计算机审计案例,如何利用计算机审计发现医院违规挂床住院