📄 main.c
字号:
//-----------------------------------------------------------------
// 名称: MODBUS总线通信仿真(从机程序)
//-----------------------------------------------------------------
// 说明: 本例运行时,从机接收主机命令,然后将A/D转换值通过485回发给主机显示.
//
//-----------------------------------------------------------------
#define _XTAL_FREQ 4000000UL
#define INT8U unsigned char
#define INT16U unsigned int
#define INT32U unsigned long
#include <pic.h>
#include <stdio.h>
//配置字,注意禁用MCLR,因为RA3/MCLR/VPP引脚用于485从机地址次高位输入
__CONFIG(MCLRDIS & WDTDIS & INTIO);
volatile INT8U recv_Data[4]; //串口接收数据缓冲区(4字节)
volatile INT8U recv_idx = 0; //串口接收数据缓冲区索引
volatile INT8U sl_Addr; //485从机地址
INT16U CRC; //16位CRC校验结果
//-----------------------------------------------------------------
#define LED_Ptr RC0 //本机收/发指示灯
#define RDE_485 RC3 //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;//相关标识位
INT8U AD_Result[2]; //2字节的A/D转换结果
//-----------------------------------------------------------------
// 宏定义: 发送一字节并等待发送结束
//-----------------------------------------------------------------
#define Send_Byte(x) \
{ \
LED_Ptr = ~LED_Ptr; \
RDE_485 = 1; \
TXREG = x; while (TRMT == 0); \
__delay_us(9); \
}
//-----------------------------------------------------------------
// 宏定义: 设置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; \
}
//-----------------------------------------------------------------
// CRC16校验函数 (基于该函数可得出512字节的校验码表,改用查表法进行校验)
// 多项式: X ^ 16 + X ^ 15 + X ^ 2 + 1, 去高位逆序表示:0xA001
//-----------------------------------------------------------------
void CRC16(INT8U d)
{
}
//-----------------------------------------------------------------
// 模/数转换函数
//-----------------------------------------------------------------
void AD_Convert()
{
__delay_us(10);
GODONE = 1; //启动转换
while (ADIF == 0); //等待转换结束
ADIF = 0; //AD中断标志清0
AD_Result[1] = ADRESH; //保存A/D结果的高字节
AD_Result[0] = ADRESL; //保存A/D结果的低字节
}
//-----------------------------------------------------------------
// 串口初始化
//-----------------------------------------------------------------
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()
{
GIE = 0; //禁止中断
TRISC = 0B00110000; //设置串行通讯端口及LED指示灯端口方向
LED_Ptr = 0; //收/发指示灯关闭
RDE_485 = 1; //禁止从机485接收
PORTA = 0x00; //初始化PORTA
WPUA = 0xFF; //使能PORTA相应引脚的弱上拉
TRISA = 0xFF; //PORTA所有引脚全部设为输入
ANSEL = 0x01; //PORTA的RA0设为模拟输入
sl_Addr = (PORTA >> 1) & 0x0F; //获取所设置的本机地址
Serial_port_init(); //串口初始化
OSCCON = 0B01101000; //使用默认的4M内部振荡器
ADCON0 = 0B10000001; //选择0通道,A/D结果右对齐,使能A/D
ADCON1 = 0B01010000; //AD转换时钟选择位ADCS<2:0>=101,选择FOSC/16
RCIE = 1; //允许串口接收中断
TMR1IE = 1; //允许TMR1溢出中断
PEIE = 1; //允许外设中断(TMR1,USART均为PIC外设)
GIE = 1; //开中断
RDE_485 = 0; //允许从机485接收(禁止发送)
TMR1ON = 1; //启动TIMER1(默认为1:1分频)
__delay_us(2);
Set_TIMER1(FRAME_SPAN); //用TIMER1控制相邻帧之间的时间间隔
Recv_OK = 0; //初始时三个标识全部设为0
}
//-----------------------------------------------------------------
// 主程序
//-----------------------------------------------------------------
void main()
{
Per_Initialize(); //外设初始化
while(1)
{
//如果本从机已经接收到完整的四个字节数据
if (Recv_OK)
{
}
}
}
//-----------------------------------------------------------------
// 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 (RCIF)
{
LED_Ptr = ~LED_Ptr; //从机收/发指示灯闪烁
R = RCREG; //从串口(来自485)读取一字节
RCIF = 0; //清标志位(此行可省略)
Recv_OK = 0; //先暂时设接收成功标志为假
//---------------------------------------------------------
//如果当前要接收的是第0字节
if (recv_idx == 0)
{
}
//---------------------------------------------------------
//否则要接收的是第0字节(即地址字节)之后的数据
else
{
}
//---------------------------------------------------------
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -