Modbus RTU проблема с отправкой пакета

всем привет, занимаюсь реализацией управления ПЧ по RS485 с avr микроконтроллера, используется микросхема с автоматическим определением отправки\приема (MAX13487). Прежде чем сразу подключать к ПЧ, проверял отправляемый пакет подключив микроконтроллер (ATmega88) через USB ↔ UART преобразователь на микросхеме CH340G, и используя программу Realterm в качестве монитора COM порта для вывода пакетов в шестнадцатеричной кодировке, но на мониторе не те команды которые были отправлены вместо того что в примере на выходе было 00 00 00 00 00 80 80.

#include <Arduino.h>
#include <avr/io.h>

#define F_CPU 16000000

unsigned char data[8] = {
  0x01, //device address
  0x06, //write mode
  0xAB, //HIGH register address 
  0xCD, //LOW register address
  0xAB, //HIGH register value
  0xCD, //LOW register value
  0x00, //HIGH CRC 
  0x00  //LOW CRC
};
int main(){
  Serial.begin(9600); //setup uart 
  Serial.setTimeout(1000);
  sendCommand(0x02,0x00,0x01,0x13,0x88); //example
  return 0;
}
uint16_t crc_16_modbus = 0xFFFF;
void sendCommand(char address, char HI_address, char LO_address, char HI_value, char LO_value){
  //make command array
  data[0] = address;
  data[2] = HI_address;
  data[3] = LO_address;
  data[4] = HI_value;
  data[5] = LO_value;
  
  //calculate CRC value 
  for(byte i=0; i<sizeof(data)-2; i++){   // -2 becouse not need CRC values
    crc_16_modbus = GetCrc16Simple(data, sizeof(data));
  }
  //update CRC  
  data[6] = crc_16_modbus & 0xFF;
  data[7] = (crc_16_modbus >> 8) & 0xFF;

  //send package modbus
  for(byte i = 0; i<sizeof(data); i++){
    Serial.write(data[i]);
  }
  Serial.flush();
  delay(10);
}

Отправьте 0x0A прямо из сетапа…

в данный момент плата в гараже, сейчас пытаюсь посоветоваться и разобраться почему так происходит. Есть ли такой момент что при отправке нуля отправка прекращается?

хм, а зачем одну и ту же контрольную сумму 6 раз считать?

1 лайк

оп. косяк спасибо там ведь полностью массив отправляю

Я и советую проделать простую операцию, без наворотов.

а надо весь массив? Наверно 2 байта CRC не надо?

Что касается проверки - казалось бы чего проще - вот прямо как есть присоедините код к терминалу и посмотрите что там отправляется.
И, кстати, где функция CRC определена?

да естественно байты CRC не берутся в расчет контрольной суммы. Метод по расчету CRC взял с этого форума:

uint16_t GetCrc16Simple(uint8_t * data, uint16_t len){
  uint8_t lo;
  union{
    uint16_t value;
    struct { uint8_t lo, hi;} bytes;
  } crc;

  crc.value = 0xFFFF;

  while(len--){
    lo = crc.bytes.hi;
    crc.bytes.lo = crc.bytes.hi;
    crc.bytes.hi = lo ^ *data++;

    uint8_t mask = 1;

    if(crc.bytes.hi & mask) crc.value ^= 0x0240;
    if ( crc.bytes.hi & ( mask << 1 ) ) crc.value ^= 0x0480;
    if ( crc.bytes.hi & ( mask << 2 ) ) crc.bytes.hi ^= 0x09;
    if ( crc.bytes.hi & ( mask << 3 ) ) crc.bytes.hi ^= 0x12;
    if ( crc.bytes.hi & ( mask << 4 ) ) crc.bytes.hi ^= 0x24;
    if ( crc.bytes.hi & ( mask << 5 ) ) crc.bytes.hi ^= 0x48;
    if ( crc.bytes.hi & ( mask << 6 ) ) crc.bytes.hi ^= 0x90;
    if ( crc.bytes.hi & ( mask << 7 ) ) crc.value ^= 0x2001;
  }
  return crc.value;
}

Выложите именно тот код, что запускаете. Полностью.
Похоже что у вас проблемы с массивом data но если вы показываете куски кода - ничего сказать нельзя.

хорошо, вот весь код

#include <Arduino.h>
#include <avr/io.h>
#include <avr/interrupt.h>  

#define F_CPU 16000000

short layer_pot, reel_pot;
volatile short old_layer_value, old_reel_value;

#define rx_pin PD0  //uart recive
#define tx_pin PD1  //uart trancive

#define OVERLOAD PD3 // INT0 interrupt connected
#define STOP PD2     // INT1 interrupt connected

#define RIGHT PD6    // layer direction right button
#define LEFT 9     // layer direction left button

#define UP 17       // reel direction up button
#define LED_UP 16   //LED in button

#define DOWN 15     //reel direction down button
#define LED_DOWN 14 //LED in button

#define ADC_REEL 20   // potentiometr pin for control speed on reel motor
#define ADC_LAYER 21  // potentiometr pin for control speed on layer motor

unsigned char data[8] = {
  0x01, //device address
  0x06, //write mode
  0xAB, //HIGH register address 
  0xCD, //LOW register address
  0xAB, //HIGH register value
  0xCD, //LOW register value
  0x00, //HIGH CRC 
  0x00  //LOW CRC
};

void load_pin(void){
    pinMode(UP, INPUT_PULLUP);
    pinMode(DOWN, INPUT_PULLUP);
    
    pinMode(LED_UP, OUTPUT);
    pinMode(LED_DOWN, OUTPUT);
    
    pinMode(LEFT, INPUT_PULLUP);
    pinMode(RIGHT, INPUT_PULLUP);
    pinMode(STOP, INPUT);
    pinMode(OVERLOAD, INPUT);
  
    pinMode(ADC_REEL, INPUT);
    pinMode(ADC_LAYER, INPUT);
//  return;
}

void load(void){
  //attach interrupt for pins OVERLOAD and STOP 
  EIMSK |= (1 << INT0)  | (1 << INT1); 
  EICRA |= (1 << ISC11) | (0 << ISC10) | (1 << ISC01) | (0 << ISC00); // falling change 1 -> 0
  Serial.begin(9600); //setup uart (default setup)
  Serial.setTimeout(1000);
  sei(); //enable interrupts
  //return;
}

volatile bool no_change = false; 

ISR(INT0_vect){ //STOP button interrupt
  sendCommand(0x00,0x00,0x00,0x00,0x00);  //shutdown all devices
  no_change = false;
  digitalWrite(LED_DOWN, LOW);
  digitalWrite(LED_UP, LOW);
  //return;
}
ISR(INT1_vect){ //OVERLOAD
  sendCommand(0x00,0x00,0x00,0x00,0x00);  //shutdown all devices
  no_change = false;
  digitalWrite(LED_DOWN, LOW);
  digitalWrite(LED_UP, LOW);
  //return;
}

int main(void) {  
  init();
  load_pin();
  load();
  while(1){
    check_keyboard();
    check_pots();
  }
  return 0;
}
//for save values in high and low bits 
unsigned char* data_layer_speed;
unsigned char* data_reel_speed;

#define DELTA 100

void check_pots(void){
  reel_pot = analogRead(ADC_REEL);
  int16_t reel_value = map(reel_pot, 0, 1023, 0, 5000); 
  if(abs(reel_value - old_reel_value) > DELTA){  // if values from potentiometr reel changed refresh freqency on slave devices
    data_reel_speed = decimalTo2ComponentHex(reel_value);
    sendCommand(0x01,0x00,0x01,data_reel_speed[0],data_reel_speed[1]);
    old_reel_value = reel_value;
  }
  layer_pot = analogRead(ADC_LAYER) + 0.2*reel_pot; 
  int16_t layer_value = map(layer_pot, 0, 1023, 0, 5000); 
  if(abs(layer_value - old_layer_value) > DELTA){  // if values from potentiometr layer changed refresh freqency on slave devices
    data_layer_speed = decimalTo2ComponentHex(layer_value);
    sendCommand(0x02,0x00,0x01,data_layer_speed[0],data_layer_speed[1]);
    old_layer_value = layer_value;
  }
}

bool click = false;
byte counter = 0;
void check_keyboard(void){ // maybe switch case?
  if(!digitalRead(UP) && !no_change){
    sendCommand(0x00,0x00,0x00,0x00,0x01); // all device start, (reel and layer drivers)
    no_change = true;            // forbidding flag
    digitalWrite(LED_UP, HIGH);   //enable LED
  }
  else if(!digitalRead(DOWN) && !no_change){
    sendCommand(0x00,0x00,0x00,0x00,0x02); // all device start reverse, (reel and layer drivers)
    no_change = true;             // forbidding flag
    digitalWrite(LED_DOWN, HIGH);  //enable LED
  }
  else if(!digitalRead(LEFT) && !click){
    counter++;
    layer_driver(false);
    click = true;
  } 
  else if(!digitalRead(RIGHT) && !click){
    counter++;
    layer_driver(true);
    click = true;
  }
  else if(click && digitalRead(RIGHT) && digitalRead(LEFT)){ 
    sendCommand(0x02,0x00,0x00,0x00,0x00);  //device shutdown
    click = false;
    counter = 0;
  }
}

void layer_driver(bool dir){
  if(counter == 1){  // when command not send, flag start not be false
    if(!dir){
      sendCommand(0x02,0x00,0x00,0x00,0x02);  //device start in reverse
      sendCommand(0x02,0x00,0x01,0x13,0x88); //set max freqency 
    }
    else{
      sendCommand(0x02,0x00,0x00,0x00,0x01);  //device start
      sendCommand(0x02,0x00,0x01,0x13,0x88); //set max freqency 
    }
  }
}


uint16_t crc_16_modbus = 0xFFFF;
void sendCommand(char address, char HI_address, char LO_address, char HI_value, char LO_value){
  //make command array
  data[0] = address;
  data[2] = HI_address;
  data[3] = LO_address;
  data[4] = HI_value;
  data[5] = LO_value;

  unsigned char buf[6] = {data[0], data[1], data[2], data[3], data[4], data[5]};
  //calculate CRC value
  crc_16_modbus = GetCrc16Simple(buf, sizeof(buf));
  //update CRC  
  data[6] = (crc_16_modbus >> 8) & 0xFF;
  data[7] = crc_16_modbus & 0xFF;
  
  //send package modbus
  for(byte i = 0; i<sizeof(data); i++){
    Serial.write(data[i]);
  }
  Serial.flush();
  delay(10);
}

uint16_t GetCrc16Simple(uint8_t * data, uint16_t len){
  uint8_t lo;
  union{
    uint16_t value;
    struct { uint8_t lo, hi;} bytes;
  } crc;

  crc.value = 0xFFFF;

  while(len--){
    lo = crc.bytes.hi;
    crc.bytes.lo = crc.bytes.hi;
    crc.bytes.hi = lo ^ *data++;

    uint8_t mask = 1;

    if(crc.bytes.hi & mask) crc.value ^= 0x0240;
    if ( crc.bytes.hi & ( mask << 1 ) ) crc.value ^= 0x0480;
    if ( crc.bytes.hi & ( mask << 2 ) ) crc.bytes.hi ^= 0x09;
    if ( crc.bytes.hi & ( mask << 3 ) ) crc.bytes.hi ^= 0x12;
    if ( crc.bytes.hi & ( mask << 4 ) ) crc.bytes.hi ^= 0x24;
    if ( crc.bytes.hi & ( mask << 5 ) ) crc.bytes.hi ^= 0x48;
    if ( crc.bytes.hi & ( mask << 6 ) ) crc.bytes.hi ^= 0x90;
    if ( crc.bytes.hi & ( mask << 7 ) ) crc.value ^= 0x2001;
  }
  return crc.value;
}

unsigned char* decimalTo2ComponentHex(unsigned int decimalNum) {
    unsigned char* result = new unsigned char[2];
    result[0] = (decimalNum >> 8) & 0xFF;
    result[1] = decimalNum & 0xFF;
    return result;
}

Вы вызываете sendCommand() в двух десятках мест в коде, в том числе в прерываниях - и все эти вызовы работают через глобальный массив data?? Не удивительно, что он у вас может портится при отсылке.
Зачем этот массив вообще нужен, если реально 5 из восьми байтов вы передаете как параметры, а еще 2 - вычисляете в самой функции? Избавьтесь от глобального массива, сделайте его локальным в функции.

И еще по мелочи - Сериал в прерывании может не работать. Так же как delay

1 лайк

спасибо за советы, исправлю.

странно, при выставленной скорости 9600 бод и переключении в терминале на скорость 1200 бод пакет отображается нормально

С фьюзами разберитесь ! То что вы в коде написали 16 000 000 не означает что фьюзы (CLKDIV8) кто то прописал правильно …

CRC разный бывает, я так считаю:

/*
  Name  : CRC-16 CCITT
  Poly  : 0x1021    x^16 + x^12 + x^5 + 1
  Init  : 0xFFFF
  Revert: false
  XorOut: 0x0000
  Check : 0x29B1 ("123456789")
  MaxLen: 4095 байт (32767 бит) - обнаружение
    одинарных, двойных, тройных и всех нечетных ошибок
*/
uint16_t crc16(uint8_t *adrBuff, uint16_t len)
{
  uint16_t crc = 0xFFFF;
  unsigned char i;

  while (len--)
  {
    crc ^= *adrBuff++ << 8;
    for (i = 0; i < 8; i++)
      crc = crc & 0x8000 ? (crc << 1) ^ 0x1021 : crc << 1;
  }
  return crc;
}

Метод по расчету CRC, у нормальных ЧП расписан в паспорте.
У Delta вот так это выглядит:

наерна моя функция отработает, если полином подставить правильный?

да думаю тоже думаю проблема в фьюзах, но вроде как выставлял на кварц 16 мгц

CRC 16 modbus по идее, сверял в калькуляторе CRC сходится. В документации к ПЧ метод расчета CRC:

Суть не в том, что сходится/не сходится, а в том, что в первую очередь нужно смотреть документацию, а не по форумам шляться.
Ну и как бы вопрос:“нахер ты лезешь в недешевое оборудование, если тебе лень даже паспорт на него почитать ?”