当前位置:首页Arduino > 正文

Arduino中Modbus设置电压值及获取电压值

作者:野牛程序员:2024-01-29 15:14:53Arduino阅读 2378


#include <ArduinoModbus.h>
#include <SoftwareSerial.h>
#include <TimerOne.h>

#define TX_PIN 2
#define RX_PIN 3

#define SETTING_MODULE_ADDRESS 1 // 设置电压值的模块地址
#define READING_MODULE_ADDRESS 2 // 获取电压值的模块地址

// 定义设置保持寄存器地址
#define SETTING_REGISTER_ADDRESS_START 0x0000
#define NUM_SETTING_REGISTERS 6

// 定义获取保持寄存器地址
#define READING_REGISTER_ADDRESS_START 0x0000
#define NUM_READING_REGISTERS 6

// 保存最新电压值的数组
uint16_t inputRegisters[NUM_READING_REGISTERS] = {0}; // 存储6个通道的电压值

// 要写入的数据数组
uint16_t dataToWrite[NUM_SETTING_REGISTERS] = {100, 200, 300, 400, 500, 600};

// 记录上次获取电压值的时间
unsigned long lastReadingTime = 0;

// 记录发送设置命令的时间
unsigned long settingCommandTime = 0;

// 定义定时器中断的时间间隔
#define TIMER_INTERVAL 10000 // 10秒

// 定义软串口用于Modbus通信
SoftwareSerial modbusSerial(TX_PIN, RX_PIN); 

void setup() {
  Serial.begin(9600); // 初始化串口用于调试信息
  modbusSerial.begin(9600); // 初始化Modbus串口

  // 设置Modbus从设备地址
  modbus_configure(&modbusSerial, SETTING_MODULE_ADDRESS, 0, 0);

  // 初始化Modbus库
  modbus_setup();

  // 设置定时器中断
  Timer1.initialize(TIMER_INTERVAL); // 设置定时器中断周期为10秒
  Timer1.attachInterrupt(timerISR);
}

void loop() {
  // 处理Modbus通信
  modbus_update();

  // 在这里可以添加其他代码,但不要阻塞主循环
}

// 定时器中断处理函数
void timerISR() {
  // 获取当前时间
  unsigned long currentTime = millis();

  // 如果在发送设置命令后的10到20秒内
  if (currentTime - settingCommandTime >= 10000 && currentTime - settingCommandTime <= 20000) {
    // 写入保持寄存器的数据
    MB_WriteNumHoldingReg_10H(SETTING_MODULE_ADDRESS, SETTING_REGISTER_ADDRESS_START, NUM_SETTING_REGISTERS, (uint8_t *)dataToWrite);
  }

  // 判断是否到了获取电压值的时间
  if (currentTime - lastReadingTime >= TIMER_INTERVAL) {
    // 如果当前时间减去上次获取的时间超过了10秒
    // 读取输入模块中的6个通道的电压值
    MB_ReadHoldingRegisters(READING_MODULE_ADDRESS, READING_REGISTER_ADDRESS_START, NUM_READING_REGISTERS);

    // 更新上次获取电压值的时间
    lastReadingTime = currentTime;
  }
}

// Modbus读保持寄存器函数
void MB_ReadHoldingRegisters(uint8_t address, uint16_t reg, uint8_t num) {
  uint8_t buffer[64]; // 假设最大消息长度为64字节
  uint16_t crc;

  modbusSerial.write(address);         // Slave Address
  modbusSerial.write(0x03);             // Function Code: Read Holding Registers
  modbusSerial.write(reg >> 8);         // Starting Address High Byte
  modbusSerial.write(reg & 0xFF);       // Starting Address Low Byte
  modbusSerial.write(num >> 8);         // Quantity of Registers High Byte
  modbusSerial.write(num & 0xFF);       // Quantity of Registers Low Byte

  crc = modbus_calc_crc(address, 0x03, reg >> 8, reg & 0xFF, num >> 8, num & 0xFF);
  modbusSerial.write(crc & 0xFF);       // CRC Low Byte
  modbusSerial.write((crc >> 8) & 0xFF); // CRC High Byte

  delay(100); // 等待足够的时间以确保设备有足够的时间响应

  // 检查串口缓冲区中是否有数据可用
  while (modbusSerial.available() > 0) {
    // 读取串口缓冲区中的数据并解析响应消息
    uint8_t length = modbusSerial.readBytes(buffer, 64);

    // 解析响应消息
    if (length >= 5) { // 最小消息长度为5个字节
      // 检查地址和功能码
      if (buffer[0] == address && buffer[1] == 0x03) {
        // 计算数据长度
        uint8_t dataLength = buffer[2];

        // 检查消息长度是否符合预期
        if (length == 5 + dataLength + 2) { // 地址(1) + 功能码(1) + 数据长度(1) + 数据 + CRC(2)
          // 解析数据
          for (int i = 0; i < dataLength / 2; i++) {
            uint16_t value = buffer[3 + i * 2] << 8 | buffer[4 + i * 2]; // 高字节在前,低字节在后
            inputRegisters[i] = value;
          }

          // 数据解析完毕,可以在这里对接收到的数据进行处理
          // 比如输出到串口或进行其他操作
          for (int i = 0; i < NUM_READING_REGISTERS; i++) {
            Serial.print("Register ");
            Serial.print(i);
            Serial.print(": ");
            Serial.println(inputRegisters[i]);
          }
        } else {
          Serial.println("Invalid message length!");
        }
      } else {
        Serial.println("Invalid address or function code!");
      }
    } else {
      Serial.println("Invalid message format!");
    }
  }
}

// 写入 N 个保持寄存器
void MB_WriteNumHoldingReg_10H(uint8_t _addr, uint16_t _reg, uint16_t _num, uint8_t *_databuf) {
  uint16_t i;
  uint16_t TxCount = 0;
  uint16_t crc = 0;
  uint8_t Tx_Buf[256]; // Assuming buffer size

  Tx_Buf[TxCount++] = _addr; // 从站地址
  Tx_Buf[TxCount++] = 0x10; // 功能码
  Tx_Buf[TxCount++] = _reg >> 8; // 寄存器地址 高字节
  Tx_Buf[TxCount++] = _reg; // 寄存器地址 低字节
  Tx_Buf[TxCount++] = _num >> 8; // 寄存器(16bits)个数 高字节
  Tx_Buf[TxCount++] = _num; // 低字节
  Tx_Buf[TxCount++] = _num << 1; // 数据个数

  for (i = 0; i < 2 * _num; i++) {
    Tx_Buf[TxCount++] = _databuf[i]; // 后面的数据长度
  }

  crc = MB_CRC16((uint8_t*)&Tx_Buf, TxCount);
  Tx_Buf[TxCount++] = crc; // crc 低字节
  Tx_Buf[TxCount++] = crc >> 8; // crc 高字节
  UART_Tx((uint8_t *)&Tx_Buf, TxCount);
}

// 计算 Modbus CRC16
uint16_t modbus_calc_crc(uint8_t address, uint8_t func, uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4) {
  uint16_t crc = 0xFFFF;
  crc = _crc16_update(crc, address);
  crc = _crc16_update(crc, func);
  crc = _crc16_update(crc, byte1);
  crc = _crc16_update(crc, byte2);
  crc = _crc16_update(crc, byte3);
  crc = _crc16_update(crc, byte4);
  return crc;
}

// CRC16 更新
uint16_t _crc16_update(uint16_t crc, uint8_t a) {
  int i;

  crc ^= a;
  for (i = 0; i < 8; ++i) {
    if (crc & 1) {
      crc = (crc >> 1) ^ 0xA001;
    } else {
      crc = (crc >> 1);
    }
  }

  return crc;
}

============================================================================

代码2:

#include <SoftwareSerial.h>
#include <ModbusMaster.h>
#include <TimerOne.h>

// 定义虚拟串口对象并指定引脚
SoftwareSerial mySerial(2, 3); // RX, TX

// 定义ModbusMaster对象
ModbusMaster modbus1; // 设备1
ModbusMaster modbus2; // 设备2

// 定义常量
const unsigned long DAC_UPDATE_INTERVAL = 10000; // DAC更新间隔,单位:毫秒(10秒)
const unsigned long ADC_READ_INTERVAL = 15000;   // ADC读取间隔,单位:毫秒(15秒)
unsigned long currentMillis;
// 定义变量
unsigned long previousDACMillis = 0;
unsigned long previousADCMillis = 0;


uint8_t result;   //创建接收缓冲区

// 定时器中断服务程序声明
void timerISR();

void setup() {
  // 初始化串口通信
  Serial.begin(9600);
  // 初始化虚拟串口
  mySerial.begin(9600);

  // 设置定时器1
  Timer1.initialize(10000); // 初始化定时器1,周期为10毫秒
  Timer1.attachInterrupt(timerISR); // 将中断服务程序与定时器1关联

  // 初始化Modbus通信,使用虚拟串口
  modbus1.begin(1, mySerial); // 使用设备地址 1
  modbus2.begin(1, mySerial); // 使用设备地址 2
}

void loop() {
  // 主循环为空,因为我们使用定时器中断进行定时
 
 // Serial.println("...");
  if (currentMillis - previousDACMillis >= DAC_UPDATE_INTERVAL) {
    Serial.println("Updating DAC...");
    // 设置DAC寄存器值
    // 示例代码,实际需要根据你的DAC模块寄存器映射进行修改
    uint16_t dacValues[6] = {1000, 2000, 3000, 4000, 5000, 6000}; // 示例的DAC值

    for (int i = 0; i < 6; i++) {
      // 设置第i个通道的DAC寄存器值
      modbus1.writeSingleRegister(0x03EE + i, dacValues[i]);
      delay(10); // 等待一段时间,确保Modbus通信完成
    }

    // 更新previousDACMillis
    previousDACMillis = currentMillis;
  }

  // 检查是否到达读取ADC的时间间隔
  if (currentMillis - previousADCMillis >= ADC_READ_INTERVAL) {
    Serial.println("Reading ADC...");
    // 读取ADC寄存器值
    uint8_t result;
    uint16_t adcValue2;

    for (int i = 0; i < 4; i++) {
      // 读取第i个通道的ADC寄存器值  
      result = modbus2.readInputRegisters(0x51B + i, 1);  
      delay(10); // 等待一段时间,确保Modbus通信完成

      // 检查通信是否成功
      if (result == modbus2.ku8MBSuccess) {
        // 获取读取到的寄存器值
        adcValue2 = modbus2.getResponseBuffer(0);

        // 打印读取到的寄存器值
        Serial.print("设备地址 2, ADC通道 ");
        Serial.print(i);
        Serial.print(" 的值为: ");
        Serial.println(adcValue2, DEC); // 将值以十进制形式输出
      } else {
        // 打印错误信息
        Serial.print("Error: ");
        Serial.println(result, HEX);
      }
    }

    // 更新previousADCMillis
    previousADCMillis = currentMillis;
  }














}

// 定时器1中断服务程序
void timerISR() {
  // 获取当前时间
  currentMillis = millis();

  // 检查是否到达更新DAC的时间间隔
 
}


野牛程序员教少儿编程与信息学奥赛-微信|电话:15892516892
野牛程序员教少儿编程与信息学竞赛-微信|电话:15892516892
相关推荐

最新推荐

热门点击