pic单片机modbus c语言,PIC单片机MODBUS总线通信仿真及程序源码

#define INT8U  unsigned char

#define INT16U unsigned int

#define INT32U unsigned long

#include

#include

#include "LM041_4BIT.h"

const char* Prompts[17] =

{

"****************",

" RS-485  MODBUS ",

"  TEST PROGRAM  ",

"****************"

};

volatile INT8U recv_Data[6];           //串口接收数据缓冲区(6字节)

volatile INT8U recv_idx = 0;           //串口接收数据缓冲区索引

volatile INT8U sl_Addr;                //485从机地址

INT8U  LCD_Buffer[16];                 //LCD显示缓冲

INT16U CRC;                            //16位CRC校验结果

//-----------------------------------------------------------------

#define  LED_Recv  RB6                 //主机接收指示灯

#define  LED_Send  RB7                 //主机发送指示灯

#define  RDE_485   RC5                 //RS485通信控制端

#define  ADC_REQ   65                  //要求从机返回A/D值的自定义命令码(范围65~72)

//19200波特率每字符时间为: 1/19200*(1+8+2) ≈  572us

//帧  间: 3.5个字符时间为: 572 * (3.5 + 1) ≈ 2574us

//字节间: 1.5个字符时间为: 572 * (1.5 + 1) ≈ 1430us

#define FRAME_SPAN  2574                  //相临帧之间的间隔时间

#define BYTE_SPAN   1430                  //帧内字节之间的间隔时间

bit     b, F_T1, T_BYTE, T_FRAME, Recv_OK;//相关标识位

//-----------------------------------------------------------------

// 宏定义: 发送一字节并等待发送结束

//-----------------------------------------------------------------

#define Send_Byte(x)                  \

{                                     \

LED_Send = 1; RDE_485 = 1;        \

TXREG = x; while (TRMT == 0);     \

__delay_us(9); LED_Send = 0;      \

}

//-----------------------------------------------------------------

// 宏定义: 设置TIMER1的定时初值并设相关标志位

//-----------------------------------------------------------------

#define Set_TIMER1(x)                \

{                                    \

TMR1H = (65536 - x) >> 8;         \

TMR1L = (65536 - x) & 0x0F;       \

TMR1IF = T_BYTE = T_FRAME = 0;    \

F_T1 = (x == FRAME_SPAN) ? 1 : 0; \

if (F_T1) recv_idx = 0;           \

}

//-----------------------------------------------------------------

// 串口初始化

//-----------------------------------------------------------------

void Serial_port_init()

{

SYNC = 0;                            //选择异步通信模式

BRGH = 1;                            //选择高速波特率发生模式

TXEN = 1;                            //允许发送数据

SPBRG = _XTAL_FREQ/16/19200 - 1;     //设置波特率为19200

SPEN = 1;                            //串行通信端口打开

CREN = 1;                            //使能连续接收串行数据

}

//-----------------------------------------------------------------

// 外设初始化(定时器,485等)

//-----------------------------------------------------------------

void Per_Initialize()

{

//端口数据方向配置

TRISC7 = TRISC6 = 1; TRISC5 = 0;     //设置连接485的三只引脚的数据方向

TRISB6 = TRISB7 = 0;                 //主机收/发指示灯引脚设为输出

//配置定时器TIMER0

T0CS = 0;                            //选择内部系统时钟源

PSA = 0; PS2 = 1;PS1 = 0; PS0 = 1;   //预分频器分配给TIMER0,PS:101->64分频

//配置USART

Serial_port_init();                  //串口初始化

RDE_485 = 1;                         //允许485发送(禁止接收)

//中断控制

RCIE = 1;                            //允许串口接收中断

TMR1IE = 1;                          //允许TMR1溢出中断

PEIE = 1;                            //允许外设中断(TMR1,USART均为PIC外设)

GIE = 1;                             //开中断

TMR1ON = 1;                          //启动TIMER1(默认为1:1分频)

}

//-----------------------------------------------------------------

// CRC16校验函数 (基于该函数可得出512字节的校验码表,改用查表法进行校验)

// 多项式: X ^ 16 + X ^ 15 + X ^ 2 + 1, 去高位逆序表示:0xA001

//-----------------------------------------------------------------

void CRC16(INT8U d)

{

}

//-----------------------------------------------------------------

// 主程序

//-----------------------------------------------------------------

void main()

{

INT8U i; INT32U  ADC_Result;

__delay_ms(100);      //等待足够时间,待从机完成初始化

Per_Initialize();     //外设初始化

LCD_Initialize();     //LCD初始化

//输出系统封面文字(4行)

for (i = 0; i < 4; i++) LCD_ShowString(i,0,(char*)Prompts[i]);

//延时10*100ms

i = 10; while (i--) __delay_ms(100);

ClearScreen();//清屏

//显示液晶上两行文字

LCD_ShowString(0,0,(char*)"   A/D Display   ");

LCD_ShowString(1,0,(char*)"-----------------");

while(1)

{

//---------------------------------------------------------

// 循环访问地址为0x01~0x04的4个485从机

//---------------------------------------------------------

for (sl_Addr = 0x01; sl_Addr <= 0x04; sl_Addr++)

{

LED_Send = 0;               //主机发送指示灯开

RDE_485 = 1;                //允许485发送(禁止接收)

Send_Byte(sl_Addr);         //(1)发送从机地址

Send_Byte(ADC_REQ);         //(2)发送操作命令码(要求从机返回A/D值)

CRC = 0xFFFF;               //CRC校验复位

CRC16(sl_Addr);             //当前从机地址校验

CRC16(ADC_REQ);             //当前操作命令码校验

Send_Byte(CRC);             //(3)发送校验码低字节

Send_Byte(CRC>>8);          //(4)发送校验码高字节

Set_TIMER1(FRAME_SPAN);     //用TIMER1控制相邻帧之间的时间间隔

Recv_OK = 0;                //先设接收成功标识为假

RDE_485 = 0;                //允许485接收(禁止发送)

//用TIMER0定时器控制接收一部从机数据的超时时间为10ms

TMR0 = (INT8U)(256 - _XTAL_FREQ / 4 / 64 * 0.01);

T0IF = 0;                   //清除T0中断标志

LED_Send = 1;               //主机发送指示灯关

//如果主机接收从机数据未完成且未超时(10ms)则等待

while(!Recv_OK && !T0IF);

//-----------------------------------------------------

//如果主机接收从数据成功则继续下面的处理

if (Recv_OK)

{   GIE = 0;                //关中断

Recv_OK = 0;            //接收成功标志重设为假

CRC = 0xFFFF;           //CRC校验初始化

//对来自当前从机的6字节数据进行CRC校验

for (i = 0; i < 6; i++) CRC16(recv_Data[i]);

//校验通过时显示

if (CRC == 0x0000)

{

LED_Send = 1;            //主机发送指示灯关闭

//从机发送的6字节分别是:站号/命令码/两字节数据(模数值)/两字节CRC

//recv_Data[2],[3]所保存的是从机返回的模/数转换值

ADC_Result = (recv_Data[2] << 8) + recv_Data[3];

//生成待显示字符串

sprintf(LCD_Buffer,

"V%d:%4.2f",sl_Addr,(float)(ADC_Result * 5.0 / 1023.0));

//发送到LCD显示(显示LCD的下两行,每行显示两组)

LCD_ShowString(

(INT8U)(sl_Addr-1)/2+2,(sl_Addr&0x01)?0:9, LCD_Buffer);

}

}

//每完成一个从机数据处理后延时10ms再开中断

__delay_ms(10); GIE = 1;

//-----------------------------------------------------

}

__delay_ms(10); //每完成一轮(4个从机)扫描后等待10ms

}

}

//-----------------------------------------------------------------

// 主机定时中断及485接收中断服务程序

//-----------------------------------------------------------------

void interrupt ISR()

{

INT8U R;

//----------------------TIMER1定时器溢出中断--------------------

if (TMR1IF)

{   TMR1IF = 0;

//F_T1: 标识TIMER1定时器当前用于实现帧间隔时间定时还时字节间隔时间定时

//F_T1 = 0时,将帧间隔时间(3.5字符)到达设为假,字节间隔时间到达设为真

//F_T1 = 1时,将帧间隔时间(3.5字符)到达设为真,字节间隔时间到达设为假

if (F_T1 == 0) { T_FRAME = 0; T_BYTE  = 1; }

else           { T_FRAME = 1; T_BYTE  = 0; }

}

//-------------------------串口接收中断-------------------------

if (RCIF)

{

LED_Recv = ~LED_Recv;                   //主机接收指示灯闪烁

R = RCREG;                              //从串口(来自485)读取一字节

RCIF = 0;                               //清标志位(此行可省略)

Recv_OK = 0;                            //先暂时设接收成功标志为假

//---------------------------------------------------------

//如果当前要接收的是第0字节

if (recv_idx == 0)

{   //如果帧间时间间隔未到,或所收到的字节与设备地址不匹配

//则重新设TIMER1定时长度为帧间时长FRAME_SPAN

if (T_FRAME == 0 || R != sl_Addr) { Set_TIMER1(FRAME_SPAN); }

//否则表示接收的第0个字节与其设备地址匹配

else

{   recv_Data[recv_idx++] = R;      //字节R保存到接收缓冲recv_Data

Set_TIMER1(BYTE_SPAN);          //重设T1定时长度为帧内字节间的时长

}

}

//---------------------------------------------------------

//否则要接收的是第0字节(即地址字节)之后的数据

else

{   //如果后续接收过程中帧内字节时间间隔未超过1.5个字符时间

if (T_BYTE == 0)

{

recv_Data[recv_idx++] = R;      //首先将有效字节保存到接收缓冲

//如果接收到的地址字节(第0字节)后的字节(即第1字节)不等于操作码

//则将接收缓冲索引归0,并重设T1定时长度为帧间隔时长

//(注意recv_idx == 2时,刚刚保存R是第1字节)

if ( recv_idx == 2 && R != ADC_REQ) { Set_TIMER1(FRAME_SPAN); }

//如果接收到来自当前从机的完整的6个字节数据

else if (recv_idx == 6)

{

Set_TIMER1(FRAME_SPAN);     //重设T1定时长度为帧间隔时长

Recv_OK = 1;                //设置接收成功标识为真

}

else { Set_TIMER1(BYTE_SPAN); } //重设T1定时长度为帧内字节间的时长

}

//同一帧内字符间的时间超时,重设T1为帧之间的时间间隔

else  { Set_TIMER1(FRAME_SPAN); }

}

//---------------------------------------------------------

}

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值