MODBUS RTU模式
Modbus协议每次发送的内容根据传输模式不同,其报文结构也会有所差异,主要分为三种。这些模式在上各有不同。在通信中,,从站(服务器)处理后。异常响应时,,例如原本的功能码0x01会变为0x81。
Modbus协议每次发送的内容根据传输模式不同,其报文结构也会有所差异,主要分为RTU、ASCII、TCP三种。这些模式在帧分隔、编码、数据域和校验方式上各有不同。
| 模式 | 典型场景 | 编码方式 | 核心区分特征 |
|---|---|---|---|
| RTU | 工业现场、PLC、变频器 | 二进制(紧凑高效) | 依靠时间间隔(3.5字符)分隔帧,使用 CRC-16 校验 |
| ASCII | 系统调试、人眼分析数据 | ASCII文本(可读性强) | 使用 冒号(:) 和 CRLF 标识帧,使用 LRC 校验 |
| TCP | 现代工业网络、物联网、高速采集 | 二进制(最高效) | 依赖 TCP 可靠性,无额外校验,通过 MBAP 报文头替代RTU帧头 |
在通信中,主站(客户端)发送请求报文,从站(服务器)处理后返回响应报文。异常响应时,功能码会加上0x80,例如原本的功能码0x01会变为0x81。
Modbus RTU 模式
RTU是工业中最常用的模式,采用紧凑的二进制编码,数据密度高。
-
帧分隔:没有固定的起始/结束字符,依靠总线上的静默时间(至少3.5个字符时间)来分隔报文。
-
报文结构:完整的RTU报文由地址、功能码、数据、CRC四个部分组成,并以特定顺序发送。
-
CRC校验:使用CRC-16循环冗余校验,保障传输数据的完整性。
-
报文示例:
01 03 00 00 00 02 C4 0B字节 数值 (Hex) 含义 1 01 地址:目标从站地址为 1 2 03 功能码:读保持寄存器 (Read Holding Registers) 3 00 数据域 4 00 数据域:寄存器起始地址为 0x00005 00 数据域 6 02 数据域:读取 2 个寄存器 7-8 C4 0B CRC校验:低字节 0xC4在前,高字节0x0B在帧长度限制:整个RTU帧的最大长度为256字节。因此,数据域(N)的最大长度受此限制,通常为252字节(256 – 地址1 – 功能码1 – CRC2)。
-
时序要求:
帧间间隔 (Inter-frame Delay) :前后两帧之间必须保持至少3.5个字符时间的静默期。接收方以此来判断一帧的结束和新一帧的开始。
帧内间隔:帧内各个字节之间的传输间隔应小于1.5个字符时间,否则接收方可能认为帧传输不完整。
“字符时间”取决于波特率(Baud Rate)。例如,在9600波特率下,传输1个字符(包括起始位、数据位、停止位,通常11位)的时间约为1.14ms。因此,3.5个字符时间约为4ms。
CRC表计算方式:
所有的 CRC 算法背后,都有一个行业规定好的方法(专业术语叫:生成多项式)。 对于 Modbus 协议来说,这个方法规定死了是:0xA001。
生成表里任意一个数字(比如索引 i,i 的范围是 0~255)的规则如下,一共分三步:
-
把索引
i放进一个 16位 的大盒子里。 -
循环 8 次(因为一个字节有 8 位):
-
看看盒子最右边的那一位(最低位)是
1还是0。 -
把盒子里所有的数字向右推移一位(最右边的那个数字被挤掉出去了)。
-
如果刚才被挤出去的那个数字是
1:就把盒子里的数字拿出来,和魔法数字0xA001做一次“异或(XOR)”运算,放回盒子里。 -
如果被挤出去的数字是
0:什么都不做,继续下一次循环。
-
-
8 次循环结束后,盒子里剩下的 16位 数字,就是这个索引最终的 CRC 结果。把它劈成两半,高八位存入
TabH,低八位存入TabL。
先准备好咱们的初始条件:
-
初始盒子里的数字:
10(十六进制写成0x000A,二进制写成0000 0000 0000 1010)。 -
方法(多项式):
0xA001(二进制写成1010 0000 0000 0001)。
接下来,开启 8 次“看尾巴 -> 推移 -> 异或”的循环:
-
第 1 次循环: 当前盒子:
0000 0000 0000 1010 -
👉 看尾巴: 最右边是
0。 -
👉 动作: 因为是 0,只向右推移一位,不异或。最左边补 0。
-
➡️ 结果:
0000 0000 0000 0101(十六进制0x0005) -
第 2 次循环: 当前盒子:
0000 0000 0000 0101 -
👉 看尾巴: 最右边是
1。 -
👉 动作: 既然是 1,就要搞大动作了!
-
先向右推移一位:变成
0000 0000 0000 0010(0x0002) -
再跟魔法数字
0xA001进行“异或”运算:0000 0000 0000 0010^1010 0000 0000 0001(异或规则:相同为0,不同为1) -
➡️ 结果:
1010 0000 0000 0011(十六进制0xA003)
-
-
第 3 次循环: 当前盒子:
1010 0000 0000 0011 -
👉 看尾巴: 最右边还是
1。继续大动作!-
先向右推移一位:变成
0101 0000 0000 0001(0x5001) -
再跟魔法数字
0xA001异或:0101 0000 0000 0001^1010 0000 0000 0001 -
➡️ 结果:
1111 0000 0000 0000(十六进制0xF000)
-
-
第 4 次循环: 当前盒子:
1111 0000 0000 0000 -
👉 看尾巴: 最右边是
0。 -
👉 动作: 太平了,只向右推移一位。
-
➡️ 结果:
0111 1000 0000 0000(十六进制0x7800) -
第 5 次循环: 当前盒子:
0111 1000 0000 0000(看最右边是0,只移位) -
➡️ 结果:
0011 1100 0000 0000(十六进制0x3C00) -
第 6 次循环: 当前盒子:
0011 1100 0000 0000(看最右边是0,只移位) -
➡️ 结果:
0001 1110 0000 0000(十六进制0x1E00) -
第 7 次循环: 当前盒子:
0001 1110 0000 0000(看最右边是0,只移位) -
➡️ 结果:
0000 1111 0000 0000(十六进制0x0F00) -
第 8 次循环: 当前盒子:
0000 1111 0000 0000(看最右边是0,最后一次移位) -
➡️ 结果:
0000 0111 1000 0000(十六进制0x0780)
见证奇迹的时刻
8 次循环终于跑完了,最后留在盒子里的 16位 最终结果是:0x0780。
我们把它一刀劈成两半:
-
高八位是:
0x07 -
低八位是:
0x80
代码实现
modbus.c
#include "modbus.h"
#include "uart.h"
// 初始化变量
Uint16 regGroup[100] = {0};
Uint16 modbus_rx_buf[MODBUS_BUF_SIZE];
Uint16 modbus_tx_buf[MODBUS_BUF_SIZE];
Uint16 modbus_rx_cnt = 0;
Uint16 modbus_rx_ready = 0;
Uint16 modbus_idle_timer = 0;
// --- CRC 表 (使用静态数组节省空间) ---
static const Uint16 TabH[] = {
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
};
static const Uint16 TabL[] = {
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04,
0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8,
0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10,
0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C,
0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0,
0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C,
0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54,
0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98,
0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40
};
// --- CRC16 计算函数 ---
Uint16 GetCRC16(Uint16 *ptr, Uint16 len)
{
Uint16 index;
Uint16 crch = 0xFF;
Uint16 crcl = 0xFF;
while (len--)
{
index = crch ^ (*ptr++ & 0xFF);
crch = crcl ^ TabH[index];
crcl = TabL[index];
}
return ((crch << 8) | crcl);
}
// --- 初始化一些测试数据供电脑读取 ---
void Modbus_Init_Regs(void)
{
regGroup[0] = 10000; // 地址 0: 目标功率 (例如 10kW = 10000W)
regGroup[1] = 2906; // 地址 1: 谐振频率 (例如 2906Hz)
regGroup[2] = 1; // 地址 2: 运行状态 (1:运行, 0:停机)
}
// --- 核心:处理接收到的一帧完整报文 ---
void Modbus_Process(void)
{
Uint16 i, cnt;
Uint16 crc_cal, crc_recv;
Uint16 tx_size = 0;
Uint16 reg_addr, reg_val;
// 1. 如果没有收到完整的一句话,或者数据太短,直接退出
if (modbus_rx_ready == 0 || modbus_rx_cnt < 8) return;
// 2. 检查设备地址是不是呼叫本机
if (modbus_rx_buf[0] != SLAVE_ADDR) goto clear_frame;
// 3. 校验 CRC 是否正确
crc_cal = GetCRC16(modbus_rx_buf, modbus_rx_cnt - 2);
// 按照 (低位<<8 | 高位) 来拼接接收到的 CRC
crc_recv = (modbus_rx_buf[modbus_rx_cnt - 2] << 8) | modbus_rx_buf[modbus_rx_cnt - 1];
// DSP字节序验证
if (crc_cal != crc_recv) goto clear_frame;
// 4. 解析功能码
switch (modbus_rx_buf[1])
{
case 0x03: // 读取保持寄存器
reg_addr = (modbus_rx_buf[2] << 8) | modbus_rx_buf[3]; // 寄存器起始地址
cnt = (modbus_rx_buf[4] << 8) | modbus_rx_buf[5]; // 读取数量
// 安全限制:防止读取越界
if ((reg_addr + cnt) <= 100)
{
modbus_tx_buf[0] = SLAVE_ADDR;
modbus_tx_buf[1] = 0x03;
modbus_tx_buf[2] = cnt * 2; // 返回的字节数
tx_size = 3;
for(i = 0; i < cnt; i++)
{
modbus_tx_buf[tx_size++] = (regGroup[reg_addr + i] >> 8) & 0xFF; // 寄存器高八位
modbus_tx_buf[tx_size++] = regGroup[reg_addr + i] & 0xFF; // 寄存器低八位
}
}
break;
case 0x06: // 写入单个寄存器
reg_addr = (modbus_rx_buf[2] << 8) | modbus_rx_buf[3];
reg_val = (modbus_rx_buf[4] << 8) | modbus_rx_buf[5];
if (reg_addr <= 100)
{
regGroup[reg_addr] = reg_val; // 修改寄存器的值
// 06 功能码的正常回复是:原样返回接收到的报文
for(i = 0; i < 6; i++) { modbus_tx_buf[i] = modbus_rx_buf[i]; }
tx_size = 6;
}
break;
default: break; // 其他功能码暂不处理
}
// 5. 如果有数据需要回复,计算发送数据的 CRC 并发送
if (tx_size > 0)
{
crc_cal = GetCRC16(modbus_tx_buf, tx_size);
// 【修改这里】:因为 crc_cal 把低位放在了高八位,所以要先发它的高八位
modbus_tx_buf[tx_size++] = (crc_cal >> 8) & 0xFF; // 实际发送的是低字节
modbus_tx_buf[tx_size++] = crc_cal & 0xFF; // 实际发送的是高字节
for (i = 0; i < tx_size; i++)
{
uarta_SendChar(modbus_tx_buf[i]);
}
}
clear_frame:
// 6. 清理战场,准备接收下一句话
modbus_rx_cnt = 0;
modbus_rx_ready = 0;
}
// --- 超时断句逻辑 (非常重要) ---
// 工业上,如果总线静默超过 3.5 个字符的时间(比如 9600波特率下约 4ms)
// 就认为上位机的一句话说完了。
void Modbus_Tick_1ms(void)
{
if (modbus_rx_cnt > 0 && modbus_rx_ready == 0)
{
modbus_idle_timer++;
if (modbus_idle_timer >= 5) // 5ms 没有收到新字节,认为一帧结束
{
modbus_rx_ready = 1;
modbus_idle_timer = 0;
}
}
}
modbus.h
#ifndef __MODBUS_H
#define __MODBUS_H
#include "headfile.h"
// 定义本机的 Modbus 地址
#define SLAVE_ADDR 0x01
// Modbus 缓冲数组大小
#define MODBUS_BUF_SIZE 128
// 全局变量声明
extern Uint16 regGroup[100]; // 你的样机状态寄存器组
extern Uint16 modbus_rx_buf[MODBUS_BUF_SIZE];
extern Uint16 modbus_tx_buf[MODBUS_BUF_SIZE];
extern Uint16 modbus_rx_cnt;
extern Uint16 modbus_rx_ready; // 接收完成一帧的标志
extern Uint16 modbus_idle_timer; // 空闲计时器
// 函数声明
void Modbus_Init_Regs(void);
void Modbus_Process(void);
void Modbus_Tick_1ms(void);
Uint16 GetCRC16(Uint16 *ptr, Uint16 len);
#endif
uart.c
#include "uart.h"
#include "headfile.h"
#include "modbus.h"
void uarta_Init(Uint32 baud)
{
EALLOW;
PieVectTable.SCIRXINTA = &uart_IRQn;
EDIS;
//计算波特率
unsigned char scihbaud=0; // 存放波特率寄存器的高 8 位
unsigned char scilbaud=0; // 存放波特率寄存器的低 8 位
Uint16 scibaud=0; // 存放计算出的完整波特率数值(16位)
scibaud = 37500000 / (8 * baud) - 1; // 公式:BRR = (LSPCLK / (波特率 * 8)) - 1 (以 LSPCLK = 37.5MHz 为前提)
scihbaud = scibaud >> 8;
scilbaud = scibaud & 0xff;
//开启SCIA时钟,调用GPIO复用配置
InitSciaGpio();
// 3. 初始化 SCI FIFO (硬件缓存区)
SciaRegs.SCIFFTX.all = 0xE040; //1110 0000 0100 0000 (开启 FIFO 功能,复位发送 FIFO,清除中断标志)
// SciaRegs.SCIFFRX.all = 0x204f;//0010 0000 0100 1111 (复位接收 FIFO,开启接收 FIFO 中断,设置接收中断级别为 15,即收到 15 个字符才进一次中断)
SciaRegs.SCIFFRX.all = 0x2021;//0010 0000 0010 0001
SciaRegs.SCIFFCT.all = 0x0;// 设置 FIFO 传输延迟为 0
// 4. 基础通信格式配置
SciaRegs.SCICCR.all = 0x0007;//0000 0000 0000 0111 1个停止位, 无校验, 8位数据位, 异步模式
SciaRegs.SCICTL1.all = 0x0003;//0000 0000 0000 0011 开启 TX (发送) 和 RX (接收) 功能
// 5. 开启中断
SciaRegs.SCICTL2.all = 0x0003;//0000 0000 0000 0011 允许发送和接收产生中断标志
// SciaRegs.SCICTL2.bit.TXINTENA =1;
// SciaRegs.SCICTL2.bit.RXBKINTENA =1;
// 6. 写入波特率并启动
SciaRegs.SCIHBAUD = scihbaud;
SciaRegs.SCILBAUD = scilbaud;
//回环测试 如果打开(写1),DSP 会把自己发送的 TX 直接在内部连到接收 RX 上,用于不接线时测试代码逻辑是否正常。
// SciaRegs.SCICCR.bit.LOOPBKENA = 1;
// 退出复位状态,正式启动 SCI 串口
SciaRegs.SCICTL1.all = 0x0023;// 0000 0000 0010 0011
PieCtrlRegs.PIEIER9.bit.INTx1 = 1;
IER |=M_INT9;
EINT;
}
// ==========================================
// 2. 发送单个字符函数
// ==========================================
void uarta_SendChar(int a)
{
// 等待发送 FIFO 缓冲里有空位
while (SciaRegs.SCIFFTX.bit.TXFFST != 0) {}
SciaRegs.SCITXBUF = a;
}
// ==========================================
// 3. 发送字符串函数
// ==========================================
void uarta_SendString(char *msg)
{
int i = 0;
while(msg[i] != '\0')
{
uarta_SendChar(msg[i]);
i++;
}
}
//回显测试
//interrupt void uart_IRQn()
//{
// Uint16 receivedChar;
//
// // 1. 读取接收到的数据
// receivedChar = SciaRegs.SCIRXBUF.all;
//
// // 2. 你的业务逻辑 (比如直接发回给电脑)
// uarta_SendChar(receivedChar);
//
// // 3. 清除硬件上的中断标志位 (必须要有)
// SciaRegs.SCIFFRX.bit.RXFFOVRCLR = 1; // 清除接收 FIFO 溢出标志
// SciaRegs.SCIFFRX.bit.RXFFINTCLR = 1; // 清除接收 FIFO 中断标志
//
// // 4. 响应 PIE 控制器,告诉它本组中断处理完毕
// PieCtrlRegs.PIEACK.all = PIEACK_GROUP9;
//}
interrupt void uart_IRQn()
{
Uint16 receivedChar;
// 1. 读取接收到的单个字节数据
receivedChar = SciaRegs.SCIRXBUF.all;
// 2. 将数据存入 Modbus 接收缓冲池,并让计数器加一
if(modbus_rx_cnt < MODBUS_BUF_SIZE)
{
modbus_rx_buf[modbus_rx_cnt++] = receivedChar & 0xFF;
}
// 3. 【极其关键】只要收到哪怕一个字节,就重置空闲计时器!
modbus_idle_timer = 0;
modbus_rx_ready = 0;
// 4. 清除硬件中断标志,准备接收下一个字节
SciaRegs.SCIFFRX.bit.RXFFOVRCLR = 1;//清除接收FIFO溢出标志
SciaRegs.SCIFFRX.bit.RXFFINTCLR = 1;//清除接收FIFO中断标志
PieCtrlRegs.PIEACK.all = PIEACK_GROUP9;
}
uart.h
#ifndef UART_H_
#define UART_H_
#include "headfile.h"
void uarta_SendChar(int a);
void uarta_SendString(char *msg);
interrupt void uart_IRQn();
void uarta_Init(Uint32 baud);
#endif /* APP_INC_UART_H_ */
main.c
#include "headfile.h"
#include "LEDS.h"
#include "KEY.h"
#include "timer.h"
#include "exit.h"
#include "adc.h"
#include "epwm.h"
#include "uart.h"
#include "modbus.h"
uint16_t key = 0,i = 50;
/**
* main.c
*/
int main(void)
{
InitSysCtrl();
MemCopy(&RamfuncsLoadStart,&RamfuncsLoadEnd,&RamfuncsRunStart);
// 1.先关闭所有中断
InitPieCtrl(); // 初始化PIE控制寄存器
IER = 0x0000; // 清除CPU级中断使能
IFR = 0x0000; // 清除CPU级中断标志
InitPieVectTable(); // 初始化PIE中断向量表
timer_Init(150,1000);
exit1_Init();
led_Init();
key_Init();
epwm1_Init(14999);
// epwm2_Init(14999);
uarta_Init(9600);
Modbus_Init_Regs();
// uarta_SendString("\r\nUART Module Initialized Successfully!\r\n");
for(;;)
{
if(led_flag ==1)
{
led_flag =0;
led_toggle(5);
}
if(modbus_rx_ready == 1)
{
Modbus_Process(); // 验证 CRC -> 读/写寄存器 -> 自动回复屏幕
if(regGroup[2] == 1) {
led_on(2);
} else {
led_off(2);
}
}
}
}
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)