#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); }
}
//---------------------------------------------------------
}
}