#include <reg52.h>
#include <string.h>
// 定义数据类型
typedef unsigned char u8;
typedef unsigned int u16;
// 串口配置
#define BAUD 9600 // 波特率
#define FREQ 11059200L // 晶振频率
// Modbus RTU 地址
#define SLAVE_ADDR 0x01 // 从站地址
// 寄存器配置
#define REG_START 0x0000 // 起始寄存器地址
#define REG_NUM 10 // 保持寄存器数量
// 缓冲区大小
#define RX_BUF_SIZE 20
#define TX_BUF_SIZE 20
// 全局变量
u8 rxBuffer[RX_BUF_SIZE]; // 接收缓冲区
u8 txBuffer[TX_BUF_SIZE]; // 发送缓冲区
u8 rxCount = 0; // 接收字节计数
u8 rxComplete = 0; // 接收完成标志
u16 holdReg[REG_NUM]; // 保持寄存器数组
// 函数声明
void UartInit(void);
u16 CRC16(u8 *buffer, u8 length);
void SendResponse(u8 *data, u8 len);
void ProcessModbus(void);
/**
* @brief 串口初始化
* 配置定时器1产生波特率,开启串口中断
*/
void UartInit(void) {
TMOD &= 0x0F;
TMOD |= 0x20; // 定时器1模式2 (8位自动重装)
TH1 = 256 - (FREQ / 12 / 32 / BAUD);
TL1 = TH1;
TR1 = 1; // 启动定时器1
SM0 = 0;
SM1 = 1; // 串口模式1 (8位UART)
REN = 1; // 允许接收
EA = 1; // 开启总中断
ES = 1; // 开启串口中断
}
/**
* @brief 计算CRC16校验码
* @param buffer 数据指针
* @param length 数据长度
* @return CRC16值
*/
u16 CRC16(u8 *buffer, u8 length) {
u16 crc = 0xFFFF;
u8 i, j;
for (i = 0; i < length; i++) {
crc ^= buffer;
for (j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc >>= 1;
crc ^= 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
/**
* @brief 发送Modbus响应数据
* @param data 数据指针
* @param len 数据长度 (不含CRC)
*/
void SendResponse(u8 *data, u8 len) {
u16 crc = CRC16(data, len);
u8 i;
ES = 0; // 关闭串口中断,防止干扰发送
// 发送数据主体
for (i = 0; i < len; i++) {
SBUF = data;
while (!TI);
TI = 0;
}
// 发送CRC低字节
SBUF = crc & 0xFF;
while (!TI);
TI = 0;
// 发送CRC高字节
SBUF = (crc >> 8) & 0xFF;
while (!TI);
TI = 0;
ES = 1; // 重新开启串口中断
}
/**
* @brief 处理Modbus请求
* 解析接收到的数据,执行相应功能码操作
*/
void ProcessModbus(void) {
u16 crc, calcCrc;
u8 addr, func;
u16 startAddr, regNum;
u8 i;
// 最小帧长度检查: Addr(1) + Func(1) + StartAddr(2) + Quantity(2) + CRC(2) = 8
if (rxCount < 8) return;
// 提取地址和功能码
addr = rxBuffer;
func = rxBuffer;
// 校验从站地址
if (addr != SLAVE_ADDR) return;
// 提取并校验CRC
crc = ((u16)rxBuffer[rxCount - 1] << 8) | rxBuffer[rxCount - 2];
calcCrc = CRC16(rxBuffer, rxCount - 2);
if (crc != calcCrc) return;
// 根据功能码处理
switch (func) {
case 0x03: // 读保持寄存器
{
u8 regAddrH = rxBuffer;
u8 regAddrL = rxBuffer;
u8 regNumH = rxBuffer;
u8 regNumL = rxBuffer;
startAddr = ((u16)regAddrH << 8) | regAddrL;
regNum = ((u16)regNumH << 8) | regNumL;
// 检查地址和数量合法性
if (startAddr + regNum > REG_NUM) {
// 异常响应: 非法数据地址 (Exception Code 0x02)
txBuffer = addr;
txBuffer = func | 0x80;
txBuffer = 0x02;
SendResponse(txBuffer, 3);
break;
}
// 构建响应帧
txBuffer = addr;
txBuffer = func;
txBuffer = regNum * 2; // 字节数
for (i = 0; i < regNum; i++) {
u16 regVal = holdReg[startAddr + i];
txBuffer[3 + 2 * i] = (regVal >> 8) & 0xFF; // 高字节
txBuffer[4 + 2 * i] = regVal & 0xFF; // 低字节
}
SendResponse(txBuffer, 3 + 2 * regNum);
break;
}
case 0x06: // 写单个寄存器
{
u8 regAddrH = rxBuffer;
u8 regAddrL = rxBuffer;
u8 regValH = rxBuffer;
u8 regValL = rxBuffer;
startAddr = ((u16)regAddrH << 8) | regAddrL;
u16 regVal = ((u16)regValH << 8) | regValL;
// 检查地址合法性
if (startAddr >= REG_NUM) {
// 异常响应: 非法数据地址
txBuffer = addr;
txBuffer = func | 0x80;
txBuffer = 0x02;
SendResponse(txBuffer, 3);
break;
}
// 写入寄存器
holdReg[startAddr] = regVal;
// 原样返回请求帧作为响应
txBuffer = addr;
txBuffer = func;
txBuffer = regAddrH;
txBuffer = regAddrL;
txBuffer = regValH;
txBuffer = regValL;
SendResponse(txBuffer, 6);
break;
}
default:
// 不支持的功能码
txBuffer = addr;
txBuffer = func | 0x80;
txBuffer = 0x01; // 非**能码
SendResponse(txBuffer, 3);
break;
}
}
/**
* @brief 串口中断服务程序
* 接收数据并判断帧结束
*/
void UartISR(void) interrupt 4 {
if (RI) {
RI = 0;
if (rxCount < RX_BUF_SIZE) {
rxBuffer[rxCount++] = SBUF;
// 简单帧结束判断:实际项目中建议使用定时器检测3.5个字符空闲时间
// 此处仅为示例,假设主循环快速处理或外部有帧结束信号
} else {
rxCount = 0; // 溢出重置
}
}
if (TI) {
TI = 0;
}
}
/**
* @brief 主函数
*/
void main(void) {
UartInit();
// 初始化寄存器默认值
for (int i = 0; i < REG_NUM; i++) {
holdReg = 0;
}
while (1) {
// 在实际应用中,应使用定时器中断来检测Modbus帧间隔(3.5字符时间)
// 这里简化处理,假设rxComplete由外部机制置位或通过延时判断
if (rxCount > 0) {
// 模拟简单的帧结束检测:如果一段时间没有新数据
// 注意:真实项目需配合定时器实现标准的Modbus RTU帧间隔检测
ProcessModbus();
rxCount = 0; // 处理完后清空缓冲区
}
}
}
|