目录
3、用宏定义对位带操作及引脚进行封装,便于编写控制继电器相关函数
4、结合产品电路框架图,对需要控制的继电器所对应的端口及管脚进行初始化
1、解析通信协议
1.物理接口
串行通信接口RS485,采用半双工异步串行通讯。
2.通信波特率
上级监控与电池巡检单元之间采用9600bps通信波特率。
3.数据格式
1位起始位,8位数据位,1位停止位,校验位:无校验。
4.通信方式
上级监控与该产品之间以交换数据包的方式进行通信。上级监控向该产品轮询数据,该产品一直处于被动状态,只有上级监控要求该产品上报数据,该产品才能发送数据
5.数据帧格式
上级监控(HOST)与该产品(SLAVE)之间交换的数据信息分为两种类型:
(1)命令信息:由HOST发出到SLAVE的命令;
(2)返回信息:由SLAVE返回到HOST的响应。
数据帧格式:
发送顺序号 | 1 | 2 | 3 | 4 |
地址 | 功能码 | 数据 | CRC校验码 | |
长度 | 8 Bit | 8 Bit | N * 8Bit | 16 Bit |
地址:
地址ADR为该产品的识别标志,一个数据总线RS485上不可挂相同地址的该产品。编址从C0H—EFH,
共80个。
功能码
该产品支持03(读数据) ,06(写单个寄存器) ,10(写多个寄存器)。
数据
上报或下设的数据,按寄存器(数据地址)进行发送,每一个寄存器由两个字节组成。
CRC校验码
CRC(Cyclical Redundancy Check)对地址、功能码和数据进行校验,由两字节组成,CRC由传输设备生成,附加在数据帧中,如果由接收到数据计算出来的校验和与附加在数据后的校验和不一致,则有错误发生。采用CRC16(ModbusRTU)格式,发送时低位在前,高位在后。
本篇只介绍其中10功能码
设置多个数据-功能码0X10
设置命令帧格式
字段值 | 字段说明 |
C0H | 地址 (拨码开关的值+0X70) |
10H | 功能码10 |
00H | 设置数据地址高字节 设置数据地址低字节 |
02H | |
00H | 数据个数高字节 数据个数低字节, |
02H | |
04H | 字节个数。等于数据个数的2倍 |
第1个数据高字节 | |
第1个数据低字节 | |
第2个数据高字节 | |
第2个数据低字节 | |
第n个数据高字节 | |
第n个数据低字节 | |
CRCHi | CRC高字节 CRC低字节 |
CRCLo |
响应帧格式
字段值 | 字段说明 |
C0H | 地址 (拨码开关的值+0XC0) |
10H | 功能码10 |
00H | 设置数据地址高字节 设置数据地址低字节 |
02H | |
00H | 数据个数高字节 数据个数低字节, |
02H | |
CRCHi | CRC高字节 CRC低字节 |
CRCLo |
每次最多可以设置16个数据
寄存地址 | 数据 | ||
0x20 | 第1个开关合闸信号 | 信号时长2S | |
0x21 | 第2个开关合闸信号 | 信号时长2S | |
0x22 | 第3个开关合闸信号 | 信号时长2S | |
0x23 | 第4个开关合闸信号 | 信号时长2S | |
0x24 | 第5个开关合闸信号 | 信号时长2S | |
0x25 | 第6个开关合闸信号 | 信号时长2S | |
0x26 | 第7个开关合闸信号 | 信号时长2S | |
0x27 | 第8个开关合闸信号 | 信号时长2S | |
. | NONE | NONE | |
. | NONE | NONE | |
0x30 | 第1个开关分闸信号 | 信号时长2S | |
0x31 | 第2个开关分闸信号 | 信号时长2S | |
0x32 | 第3个开关分闸信号 | 信号时长2S | |
0x33 | 第4个开关分闸信号 | 信号时长2S | |
0x34 | 第5个开关分闸信号 | 信号时长2S | |
0x35 | 第6个开关分闸信号 | 信号时长2S | |
0x36 | 第7个开关分闸信号 | 信号时长2S | |
0x37 | 第8个开关分闸信号 | 信号时长2S |
2、配置485通信的接收与发送
串口初始化及接收和发送函数的编写如下
#include "rs485.h"
#include "SysTick.h"
//接收缓存区
u8 RS485_RX_BUF[64]; //接收缓冲,最大64个字节.
//接收到的数据长度
u8 RS485_RX_CNT=0,Sendflag;
//初始化IO 串口2
//pclk1:PCLK1时钟频率(Mhz)
//bound:波特率
void RS485_Init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//PA3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
// RCC_APB1PeriphResetCmd(RCC_APB1Periph_USART2,ENABLE);//复位串口2
// RCC_APB1PeriphResetCmd(RCC_APB1Periph_USART2,DISABLE);//停止复位
USART_InitStructure.USART_BaudRate = bound;//波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位数据长度
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(USART2, &USART_InitStructure); ; //初始化串口
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //使能串口2中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; //先占优先级2级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级2级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启中断
// USART_ClearFlag(USART2,USART_IT_RXNE);
USART_Cmd(USART2, ENABLE); //使能串口
}
void USART2_IRQHandler(void)
{
u8 res;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收到数据
{
res =USART_ReceiveData(USART2); //读取接收到的数据
if(RS485_RX_CNT<64)
{
RS485_RX_BUF[RS485_RX_CNT]=res; //记录接收到的值
RS485_RX_CNT++; //接收数据增加1
}
Sendflag=1;
}
}
void RS485_Send_Data(u8 *buf,u8 len)
{
u8 t;
for(t=0;t<len;t++)
{
while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
USART_SendData(USART2,buf[t]);
}
while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
RS485_RX_CNT=0;
Sendflag=0;
}
//RS485查询接收到的数据
//buf:接收缓存首地址
//len:读到的数据长度
void RS485_Receive_Data(u8 *buf,u8 *len)
{
u8 rxlen=RS485_RX_CNT;
u8 i=0;
*len=0; //默认为0
delay_ms(2); //等待2ms,连续超过2ms没有接收到一个数据,则认为接收结束
if(rxlen==RS485_RX_CNT&&rxlen)//接收到了数据,且接收完成了
{
for(i=0;i<rxlen;i++)
{
buf[i]=RS485_RX_BUF[i];
}
*len=RS485_RX_CNT; //记录本次数据长度
RS485_RX_CNT=0; //清零
}
}
3、用宏定义对位带操作及引脚进行封装,便于编写控制继电器相关函数
#ifndef _COMMUNICATION_H
#define _COMMUNICATION_H
#define RLYA_PIN GPIO_Pin_12|GPIO_Pin_11|GPIO_Pin_10|GPIO_Pin_9|GPIO_Pin_8|GPIO_Pin_7|GPIO_Pin_6|GPIO_Pin_4|GPIO_Pin_5
#define RLYB_PIN GPIO_Pin_15|GPIO_Pin_14|GPIO_Pin_12|GPIO_Pin_11|GPIO_Pin_10|GPIO_Pin_1|GPIO_Pin_0
#define RLY1ON PAout(12)=1
#define RLY1OFF PAout(12)=0
#define RLY2ON PAout(11)=1
#define RLY2OFF PAout(11)=0
#define RLY3ON PAout(10)=1
#define RLY3OFF PAout(10)=0
#define RLY4ON PAout(9)=1
#define RLY4OFF PAout(9)=0
#define RLY5ON PAout(8)=1
#define RLY5OFF PAout(8)=0
#define RLY6ON PBout(15)=1
#define RLY6OFF PBout(15)=0
#define RLY7ON PBout(14)=1
#define RLY7OFF PBout(14)=0
#define RLY8ON PBout(12)=1
#define RLY8OFF PBout(12)=0
#define RLY9ON PBout(11)=1
#define RLY9OFF PBout(11)=0
#define RLY10ON PBout(10)=1
#define RLY10OFF PBout(10)=0
#define RLY11ON PBout(1)=1
#define RLY11OFF PBout(1)=0
#define RLY12ON PBout(0)=1
#define RLY12OFF PBout(0)=0
#define RLY13ON PAout(7)=1
#define RLY13OFF PAout(7)=0
#define RLY14ON PAout(6)=1
#define RLY14OFF PAout(6)=0
#define RLY15ON PAout(4)=1
#define RLY15OFF PAout(4)=0
#define RLY16ON PAout(5)=1
#define RLY16OFF PAout(5)=0
extern unsigned char ACT_FLAG;
void RLY_Init(void);
void IN_state_Init(void);
//extern void IN_state(char num1,char sta1);
extern void RLY_action(char num,char sta);
extern unsigned short currlevel[100];
void readcurrlevel(void);
#endif
4、结合产品电路框架图,对需要控制的继电器所对应的端口及管脚进行初始化
代码如下(示例):
#include "communication.h"
#include "system.h"
unsigned short currlevel[100];
void RLY_Init()
{
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Pin=RLYA_PIN; //选择你要设置的IO口
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//下拉输入
GPIO_Init(GPIOA,&GPIO_InitStructure); /* 初始化GPIO */
GPIO_InitStructure.GPIO_Pin=RLYB_PIN;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//下拉输入
GPIO_Init(GPIOB,&GPIO_InitStructure); /* 初始化GPIO */
GPIO_ResetBits(GPIOA,RLYA_PIN);
GPIO_ResetBits(GPIOB,RLYB_PIN);
}
void RLY_action(char num,char sta)
{
switch(num)
{
case 0:
{
if(sta==1) RLY1ON;else RLY1OFF;break;
}
case 1:
{
if(sta==1) RLY3ON;else RLY3OFF;break;
}
case 2:
{
if(sta==1) RLY5ON;else RLY5OFF;break;
}
case 3:
{
if(sta==1) RLY7ON;else RLY7OFF;break;
}
case 4:
{
if(sta==1) RLY9ON;else RLY9OFF;break;
}
case 5:
{
if(sta==1) RLY11ON;else RLY11OFF;break;
}
case 6:
{
if(sta==1) RLY13ON;else RLY13OFF;break;
}
case 7:
{
if(sta==1) RLY15ON;else RLY15OFF;break;
}
case 8:
{
if(sta==1) RLY2ON;else RLY2OFF;break;
}
case 9:
{
if(sta==1) RLY4ON;else RLY4OFF;break;
}
case 10:
{
if(sta==1) RLY6ON;else RLY6OFF;break;
}
case 11:
{
if(sta==1) RLY8ON;else RLY8OFF;break;
}
case 12:
{
if(sta==1) RLY10ON;else RLY10OFF;break;
}
case 13:
{
if(sta==1) RLY12ON;else RLY12OFF;break;
}
case 14:
{
if(sta==1) RLY14ON;else RLY14OFF;break;
}
case 15:
{
if(sta==1) RLY16ON;else RLY16OFF;break;
}
}
}
5、使用定时器中断控制指示灯闪烁(减少cpu的占用)并对继电器打开动作做延时处理
#include "time.h"
#include "led.h"
#include "SysTick.h"
unsigned char FLAG_BUF[16]={2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2},ACT_FLAG2=0;
/*******************************************************************************
* 函 数 名 : TIM4_Init
* 函数功能 : TIM4初始化函数
* 输 入 : per:重装载值
psc:分频系数
* 输 出 : 无
*******************************************************************************/
void TIM4_Init(u16 per,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);//使能TIM4时钟
TIM_TimeBaseInitStructure.TIM_Period=per; //自动装载值
TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //分频系数
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //设置向上计数模式
TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructure);
TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE); //开启定时器中断
TIM_ClearITPendingBit(TIM4,TIM_IT_Update);
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;//定时器中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM4,ENABLE); //使能定时器
}
/*******************************************************************************
* 函 数 名 : TIM4_IRQHandler
* 函数功能 : TIM4中断函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void TIM4_IRQHandler(void)
{
static u32 time1=0,time2=0,time3=0,time4=0,time5=0,time6=0,time7=0,time8=0,time9=0,time10=0,time11=0,time12=0,time13=0,time14=0,time15=0,time16=0,time17=0;
if(TIM_GetITStatus(TIM4,TIM_IT_Update))
{
time1++;
if(time1%10==0)
LED1=!LED1;
if(FLAG_BUF[0]==1)
{
ACT_FLAG2=1;
time2++;
if(time2%40==0)
FLAG_BUF[0]=0;
}
if(FLAG_BUF[1]==1)
{
ACT_FLAG2=1;
time3++;
if(time3%40==0)
FLAG_BUF[1]=0;
}
if(FLAG_BUF[2]==1)
{
ACT_FLAG2=1;
time4++;
if(time4%40==0)
FLAG_BUF[2]=0;
}
if(FLAG_BUF[3]==1)
{
ACT_FLAG2=1;
time5++;
if(time5%40==0)
FLAG_BUF[3]=0;
}
if(FLAG_BUF[4]==1)
{
ACT_FLAG2=1;
time6++;
if(time6%40==0)
FLAG_BUF[4]=0;
}
if(FLAG_BUF[5]==1)
{
ACT_FLAG2=1;
time7++;
if(time7%40==0)
FLAG_BUF[5]=0;
}
if(FLAG_BUF[6]==1)
{
ACT_FLAG2=1;
time8++;
if(time8%40==0)
FLAG_BUF[6]=0;
}
if(FLAG_BUF[7]==1)
{
ACT_FLAG2=1;
time9++;
if(time9%40==0)
FLAG_BUF[7]=0;
}
if(FLAG_BUF[8]==1)
{
ACT_FLAG2=1;
time10++;
if(time10%40==0)
FLAG_BUF[8]=0;
}
if(FLAG_BUF[9]==1)
{
ACT_FLAG2=1;
time11++;
if(time11%40==0)
FLAG_BUF[9]=0;
}
if(FLAG_BUF[10]==1)
{
ACT_FLAG2=1;
time12++;
if(time12%40==0)
FLAG_BUF[10]=0;
}
if(FLAG_BUF[11]==1)
{
ACT_FLAG2=1;
time13++;
if(time13%40==0)
FLAG_BUF[11]=0;
}
if(FLAG_BUF[12]==1)
{
ACT_FLAG2=1;
time14++;
if(time14%40==0)
FLAG_BUF[12]=0;
}
if(FLAG_BUF[13]==1)
{
ACT_FLAG2=1;
time15++;
if(time15%40==0)
FLAG_BUF[13]=0;
}
if(FLAG_BUF[14]==1)
{
ACT_FLAG2=1;
time16++;
if(time16%40==0)
FLAG_BUF[14]=0;
}
if(FLAG_BUF[15]==1)
{
ACT_FLAG2=1;
time17++;
if(time17%40==0)
FLAG_BUF[15]=0;
}
}
TIM_ClearITPendingBit(TIM4,TIM_IT_Update);
}
6、主函数的逻辑结构设计与编写
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "rs485.h"
#include "crc16.h"
#include "communication.h"
#include "time.h"
int main()
{
u8 m=0,n=0,p=0;
u8 rs485buf[100]; //接收缓存数组
unsigned char TX_BUFF[100],inputdata[64],on_flagbuf[64]; //发送缓存数组
unsigned char ACT_FLAG=0;
unsigned short currlevelchange[100]; //存放改变开关状态后的电位情况
u8 len=0;
unsigned int CRCcode;
unsigned short j=0,k,i=0;
unsigned short RegNum,RegStart,NumH,NumL;
// unsigned char Addr=0;
SysTick_Init(64);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2组
LED_Init();
RLY_Init();
IN_state_Init();
RS485_Init(9600);
TIM4_Init(10,36000-1);
// RLY_action(0,1); //打开继电器1
while(1)
{
if(ACT_FLAG2==0)
RS485_Receive_Data(rs485buf,&len);
readcurrlevel();
if(rs485buf[0]==0XC0)//KMT协议 rs485buf[0]==0XC0
{
NumH = rs485buf[2];
NumL = rs485buf[3];
RegStart = NumH*256+NumL;//起始地址
NumH = rs485buf[4];
NumL = rs485buf[5];
RegNum = NumH*256+NumL;//请求数量
for(m=0;m<2;m++)
TX_BUFF[m]=rs485buf[m];
if(rs485buf[1]==0x10)
{
CRCcode = GetCRC16(&rs485buf[0],(RegNum*2+7));
NumH = CRCcode>>8;
NumL = CRCcode&0xff;
if(rs485buf[RegNum*2+7]==NumH&&rs485buf[RegNum*2+8]==NumL)
{
// if(ACT_FLAG2==0)
// {
if((RegStart+RegNum)<0x29)
{
for(k=0;k<16;k++)
{
if(FLAG_BUF[k]!=2)
break;
}
if(k==16)
{
for(k=RegStart-32;k<(RegStart-32+RegNum);k++)
{
j+=2;
inputdata[j+6]=rs485buf[j+6]&0xff;
if(inputdata[j+6]==1)
{
RLY_action(k,1);
// on_flagbuf[p]=k;
// p++;
// FLAG_BUF[k]=1;
}
}
}
for(k=0;k<16;k++)
{
if(FLAG_BUF[k]!=2)
break;
}
if(k==16)
{
for(k=RegStart-32;k<(RegStart-32+RegNum);k++)
{
i+=2;
inputdata[i+6]=rs485buf[i+6]&0xff;
if(inputdata[i+6]==1)
{
on_flagbuf[p]=k;
p++;
FLAG_BUF[k]=1;
}
}
}
readcurrlevel();
for(n=0;n<8;n++)
{
currlevelchange[n]=currlevel[n];
}
if(FLAG_BUF[on_flagbuf[0]]==0)
for(n=0;n<p;n++)
{
if(FLAG_BUF[on_flagbuf[n]]==0)
{
RLY_action(on_flagbuf[n],0);
FLAG_BUF[on_flagbuf[n]]=2;
}
}
}
if((RegStart+RegNum)>0x30)
// if(0)
{
for(k=0;k<16;k++)
{
if(FLAG_BUF[k]!=2)
break;
}
if(k==16)
{
for(k=RegStart-40;k<(RegStart-40+RegNum);k++)
{
j+=2;
inputdata[j+6]=rs485buf[j+6]&0xff;
if(inputdata[j+6]==1)
{
RLY_action(k,1);
// on_flagbuf[p]=k;
// p++;
// FLAG_BUF[k]=1;
}
}
}
for(k=0;k<16;k++)
{
if(FLAG_BUF[k]!=2)
break;
}
if(k==16)
{
for(k=RegStart-40;k<(RegStart-40+RegNum);k++)
{
i+=2;
inputdata[i+6]=rs485buf[i+6]&0xff;
if(inputdata[i+6]==1)
{
on_flagbuf[p]=k;
p++;
FLAG_BUF[k]=1;
}
}
}
readcurrlevel();
for(n=0;n<8;n++)
{
currlevelchange[n]=currlevel[n];
}
if(FLAG_BUF[on_flagbuf[0]]==0)
for(n=0;n<p;n++)
{
if(FLAG_BUF[on_flagbuf[n]]==0)
{
RLY_action(on_flagbuf[n],0);
FLAG_BUF[on_flagbuf[n]]=2;
}
}
}
if(n==p)
{
ACT_FLAG=1;
p=0;
j=0;
i=0;
ACT_FLAG2=0;
}
for(m=0;m<6;m++)
TX_BUFF[m]=rs485buf[m];
CRCcode = GetCRC16(&TX_BUFF[0] ,6);
NumH = CRCcode >> 8;
NumL = CRCcode & 0xff;
TX_BUFF[6] = NumH;
TX_BUFF[7] = NumL;
if(Sendflag)
RS485_Send_Data(TX_BUFF,8);
if(ACT_FLAG==1)
{
for(k=0;k<(RegNum*2+8);k++)
{
rs485buf[k]=0;
}
ACT_FLAG=0;
}
}
}
总结
以上就是今天要讲的内容,本文仅仅介绍了10功能码的程序设计。为保证产品能多次执行相同或不同的上位机指令,主函数中设置了多个闭环,用来保证后来指令不对当前指令造成干扰。