单工通信只支持信号在一个方向上传输(正向或反向),任何时候不能改变信号的传输方向。为保证正确传送数据信号,接收端要对接收的数据进行校验,若校验出错,则通过监控信道发送请求重发的信号。此种方式适用于数据收集系统,如气象数据的收集、电话费的集中计算等。例如计算机和打印机之间的通信是单工模式,因为只有计算机向打印机传输数据,而没有相反方向的数据传输。还有在某些通信信道中,如单工无线发送等。
半双工通信允许信号在两个方向上传输,但某一时刻只允许信号在一个信道上单向传输。因此,半双工通信实际上是一种可切换方向的单工通信。此种方式适用于问讯、检索、科学计算等数据通信系统;传统的对讲机使用的就是半双工通信方式。由于对讲机传送及接收使用相同的频率,不允许同时进行。因此一方讲完后,需设法告知另一方讲话结束(例如讲完后加上’OVER’),另一方才知道可以开始讲话。
全双工通信允许数据同时在两个方向上传输,即有两个信道,因此允许同时进行双向传输。全双工通信是两个单工通信方式的结合,要求收发双方都有独立的接收和发送能力。全双工通信效率高,控制简单,但造价高。计算机之间的通信是全双工方式。一般的电话、手机也是全双工的系统,因为在讲话时可以听到对方的声音。
参考链接: https://blog.csdn.net/iningwei/article/details/100134783
主从模式,是数据库设计模式中最常见、也是大家日常设计工作中用的最多的一种模式,它描述了两个表之间的主从关系,是典型的“一对多”关系。
规定要求:
1. 系统中只有一个设备时主机
2. 系统中的所有从机不可以主动向主机发数据
3. 系统中的主机和所有从机上电后都处于监听状态
4. 任何一次的数据交换都要由主机发起
4.1、将自己转为发送状态
4.2、主机按照预先约定的格式,发出寻址数据帧
4.3、恢复自己的接受状态,等待所寻址的从机响应
整个系统只能有一个主机,每个从机必须有一个唯一的地址(0~247)
其中0号地址位广播地址:主机向0号地址的设备发数据包,也就是要把该数据包发给所有的从设备。0号地址的数据包所有从机是不回应的。
>MODBUS的两种传输方式:RTU方式和ASC方式
>
>RTU方式:也叫十六进制 例如:发送0x03:0000 0011
>
>RTU方式:也叫十六进制 例如:发送0x03:0000 0011
>
>ASC方式:0x03 {发送0 :0x30:0011 0000 }{ 发送3:0x33:0011 0011}
>
>所以ASC的通信效率低,但是方便调试,使用实验;工业上都采用RTU方式,效率高
1、从机地址 2、功能码(127个) 3、数据1~数据n 4、校验码(CRCL、CRCH)
其中: 1~3参与CRC16校验
从机是以接收数据停止时间达到3.5个字节以上,那么就认为主机的寻址帧完成,并开始处理。
例如:波特率:9600bt/s
所以每位数据传输的时间T=1000000us/9600=104us
一字节时间位=10T=1004us(起始位 8位 停止位)(串口格式)
所以时间为:3.5*10T=3645us
1、: 2、地址 3、功能码 4、数据1~数据n 5、(地址数据)采用LRC校验=((地址+功能码+数据1数据n)%256)+1=(0255) 6、13 10(回车 换行)
1.将一个 16 位寄存器装入十六进制 FFFF (全 1). 将之称作 CRC 寄存器.
2.将报文的第一个 8 位字节与 16 位 CRC 寄存器的低字节异或,结果置于 CRC 寄存器.
3.将 CRC 寄存器右移 1 位 (向 LSB 方向), MSB 充零. 提取并检测 LSB.
4.(如果 LSB 为 0): 重复步骤 3 (另一次移位).
(如果 LSB 为 1): 对 CRC 寄存器异或多项式值 0xA001 (1010 0000 0000 0001).
5.重复步骤 3 和 4,直到完成 8 次移位。当做完此操作后,将完成对 8 位字节的完整操作。
6.对报文中的下一个字节重复步骤 2 到 5,继续此操作直至所有报文被处理完毕。
7.CRC 寄存器中的最终内容为 CRC 值.
8.当放置 CRC 值于报文时,高低字节必须交换。
SysClock_Configuration(RCC_PLLSource_HSE_Div1,RCC_CFGR_PLLMULL9);//设置系统时钟,外部设置为72MHZ,内部设置为64MHZ
void BASIC_TIM_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
BASIC_TIM_APBxClock_FUN(BASIC_TIM_CLK, ENABLE); //开启定时器时钟,即内部时钟CK_INT=72M
TIM_TimeBaseStructure.TIM_Period=TIM6_Period; //自动重装载寄存器周的值(计数值)
// 累计TIM_Period 个频率后产生一个更新或者中断
// 时钟预分频数为71,则驱动计数器的时钟CK_CNT = CK_INT / (71+1)=1M
TIM_TimeBaseStructure.TIM_Prescaler= TIM6_Prescaler;
//TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; // 时钟分频因子 ,基本定时器没有,不用管
//TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; // 计数器计数模式,基本定时器只能向上计数,没有计数模式的设置
//TIM_TimeBaseStructure.TIM_RepetitionCounter=0; // 重复计数器的值,基本定时器没有,不用管
TIM_TimeBaseInit(BASIC_TIM, &TIM_TimeBaseStructure); // 初始化定时器
TIM_ClearFlag(BASIC_TIM, TIM_FLAG_Update); // 清除计数器中断标志位
TIM_ITConfig(BASIC_TIM,TIM_IT_Update,ENABLE); // 开启计数器中断
TIM_Cmd(BASIC_TIM, ENABLE); // 使能计数器
//BASIC_TIM_APBxClock_FUN(BASIC_TIM_CLK, DISABLE); // 暂时关闭定时器的时钟,等待使用
}
基本定时器头文件
#ifdef BASIC_TIM6 // 使用基本定时器TIM6
#define BASIC_TIM TIM6
#define BASIC_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define BASIC_TIM_CLK RCC_APB1Periph_TIM6
#define BASIC_TIM_IRQ TIM6_IRQn
#define BASIC_TIM_IRQHandler TIM6_IRQHandler
#define TIM6_Period (1000)
#define TIM6_Prescaler (72-1)
定时器中断函数
void BASIC_TIM_IRQHandler (void) //定时器中断函数
{
if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET )
{
TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update);
}
}
配置定时器中断使能
void ALL_NVIC_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); // 设置中断组为1
NVIC_InitStructure.NVIC_IRQChannel = BASIC_TIM_IRQ ; // 设置中断来源
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 设置主优先级为 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; // 设置抢占优先级为3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);C
}
主程序结构
int main(void)
{
SysClock_Configuration(RCC_PLLSource_HSE_Div1,RCC_CFGR_PLLMULL9);//设置系统时钟,外部设置为72MHZ,内部设置为64MHZ
BASIC_TIM_Config(); //定时器配置为1MS
ALL_NVIC_Init(); //配置中断优先级
}
运行程序,判断是否到定时器中断中的断点完成定时器1MS定时
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE); // 打开串口GPIO 的时钟
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE); // 打开串口外设的时钟
// 将USART1 Tx 的GPIO 配置为推挽复用模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
// 将USART Rx 的GPIO 配置为浮空输入模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
// 配置串口的工作参数
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE; // 配置波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 配置 针数据字长
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 配置停止位
USART_InitStructure.USART_Parity = USART_Parity_No ; // 配置校验位
USART_InitStructure.USART_HardwareFlowControl =USART_HardwareFlowControl_None; // 配置硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 配置工作模式,收发一起
USART_Init(DEBUG_USART, &USART_InitStructure); // 完成串口的初始化配置
USART_ITConfig(DEBUG_USART, USART_IT_RXNE, ENABLE); // 使能串口接收中断
USART_Cmd(DEBUG_USART, ENABLE); // 使能串口
}
串口头文件
// 串口2-USART1
#define DEBUG_USART USART2
#define DEBUG_USART_CLK RCC_APB1Periph_USART2
#define DEBUG_USART_APBxClkCmd RCC_APB1PeriphClockCmd
#define DEBUG_USART_BAUDRATE 9600
// USART GPIO 引脚宏定义
#define DEBUG_USART_GPIO_CLK RCC_APB2Periph_GPIOA
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORT GPIOA
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_2
#define DEBUG_USART_RX_GPIO_PORT GPIOA
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_3
// USART GPIO 中断
#define DEBUG_USART_IRQ USART2_IRQn
#define DEBUG_USART_IRQHandler USART2_IRQHandler
串口中断函数
void DEBUG_USART_IRQHandler(void)
{
uint8_t ucTemp;
if (USART_GetITStatus(DEBUG_USART,USART_IT_RXNE)!=RESET) //判断是否有数据接收
{
ucTemp = USART_ReceiveData( DEBUG_USART ); //将接收的一个字节保存
USART_SendData(DEBUG_USART,ucTemp); //保存后发送调试助手,
}
}
串口中断使能函数
void ALL_NVIC_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); // 设置中断组为1
NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ ; // 设置中断来源
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 设置主优先级为 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 设置抢占优先级为0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
主函数结构
int main(void)
{
SysClock_Configuration(RCC_PLLSource_HSE_Div1,RCC_CFGR_PLLMULL9);//设置系统时钟,外部设置为72MHZ,内部设置为64MHZ
USART_Config();
ALL_NVIC_Init();
}
运行程序,判断是否到串口中断中的断点完成串口配置
当串口接受完数据,开启定时器计数,当时间>8T就开始处理数据
此时需要配置MODBUS的参数,如下
typedef struct
{
unsigned char myadd; //本设备的地址
unsigned char rcbuf[100]; //MODBUS接收缓冲区
unsigned int timout; //MODbus的数据断续时间
unsigned char recount; //MODbus端口已经收到的数据个数
unsigned char timrun; //MODbus定时器是否计时的标志
unsigned char reflag; //收到一帧数据的标志
unsigned char Sendbuf[100]; //MODbus发送缓冲区
}MODBUS;
extern MODBUS modbus; //声明全局变量,然后在C文件中调用
串口中断配置如下
void DEBUG_USART_IRQHandler(void)
{
uint8_t ucTemp;
if (USART_GetITStatus(DEBUG_USART,USART_IT_RXNE)!=RESET) //判断是否有数据接收
{
ucTemp = USART_ReceiveData( DEBUG_USART ); //将接收的一个字节保存
modbus.rcbuf[modbus.recount++]=ucTemp; //保存到MODBUS的接收缓存区
modbus.timout=0; //串口接收数据的过程中,定时器不计时
if(modbus.recount==1) //收到主机发来的一帧数据的第一字节
{
modbus.timrun=1; //启动定时
}
}
}
定时器中断配置如下
void BASIC_TIM_IRQHandler (void) //定时器中断函数
{
if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET )
{
if(modbus.timrun!=0) //串口发送数据是否结束,结束就让定时器定时
{
modbus.timout++; //定时器定时1毫秒,并开始记时
if(modbus.timout>=8) //间隔时间达到了时间,假设为8T,实际3.5T即可
{
modbus.timrun=0;//关闭定时器--停止定时
modbus.reflag=1;//收到一帧数据,开始处理数据
}
}
TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update);
}
}
运行程序,判断是否进入到定时器处理modbus.reflag=1;
void Mosbus_Event(void)
{
u16 crc;
u16 rccrc;
if(modbus.reflag==0) //没有收到MODbus的数据包
{
return ; //没有收到处理指令,继续等待下一条数据
}
crc= crc16(&modbus.rcbuf[0], modbus.recount-2); //计算校验码
rccrc=modbus.rcbuf[modbus.recount-2]*256 + modbus.rcbuf[modbus.recount-1]; //收到的校验码
if(crc == rccrc) //数据包符合CRC校验规则
{
if(modbus.rcbuf[0] == modbus.myadd) //确认数据包是否是发给本设备的
{
switch(modbus.rcbuf[1]) //分析功能码
{
case 0: break;
case 1: break;
case 2: break;
case 3: Modbud_fun3(); break; //3号功能码处理
case 4: break;
case 5: break;
case 6: Modbud_fun6(); break; //6号功能码处理
case 7: break;
}
}
else if(modbus.rcbuf[0] == 0) //广播地址,不处理
{
}
} //数据包不符合CRC校验规则
modbus.recount=0; //清除缓存计数
modbus.reflag=0; //重新开始执行处理函数C
}
处理流程图
6号功能码程序
void Modbud_fun6() //6号功能码处理,写寄存器
{
unsigned int Regadd;
unsigned int val;
unsigned int i,crc,j;
i=0;
Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3]; //得到要修改的地址
val=modbus.rcbuf[4]*256+modbus.rcbuf[5]; //修改后的值
Reg[Regadd]=val; //修改本设备相应的寄存器
//以下为回应主机
modbus.Sendbuf[i++]=modbus.myadd; //发送本设备地址
modbus.Sendbuf[i++]=0x06; //发送功能码
modbus.Sendbuf[i++]=Regadd/256; //发送修改地址高位
modbus.Sendbuf[i++]=Regadd%256; //发送修改地址低位
modbus.Sendbuf[i++]=val/256; //发送修改的值高位
modbus.Sendbuf[i++]=val%256; //发送修改的值低位
crc=crc16(modbus.Sendbuf,i); //校验地址、功能码、地址、数据
modbus.Sendbuf[i++]=crc/256; //发送CRC的值高位
modbus.Sendbuf[i++]=crc%256; //发送CRC的值低位
for(j=0;j
3号功能码程序
void Modbud_fun3(void) //3号功能码处理 ---主机要读取本从机的寄存器
{
u16 Regadd;
u16 Reglen;
u16 byte;
u16 i,j;
u16 crc;
Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3]; //得到要读取的寄存器的首地址
Reglen=modbus.rcbuf[4]*256+modbus.rcbuf[5]; //得到要读取的寄存器的数量
i=0;
modbus.Sendbuf[i++]=modbus.myadd; //发送本设备地址
modbus.Sendbuf[i++]=0x03; //发送功能码
byte=Reglen*2; //要返回的数据字节数
//modbus.Sendbuf[i++]=byte/256;
modbus.Sendbuf[i++]=byte%256; //发送要返回的数据字节数
for(j=0;j