Почему я получаю символы со смещением в буфере при использовании ReadFile()?

Что я пытаюсь сделать:

Я пытаюсь написать консоль Win, которая будет связываться с моей платой ATMega2560 через UART. Теперь он должен отправить строку, сохраненную в stringToSend, на MCU, который должен быть отправлен обратно на ПК. Строка, отправленная из MCU, должна быть сохранена в receivedString, а затем записана в окно консоли Win.

Что делает код:

Что он делает сейчас, так это то, что после того, как я отправлю строку в MCU, она вернется, но на консоли я вижу неожиданную последовательность символов «╠», предшествующую эхо-строке, вот так:

Ты отправил:

╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠ ╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠ ╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠Отправлена ​​тестовая строка!


Обновлено: количество символов постоянно, если BUFFER_SIZE постоянно, но масштабируется с BUFFER_SIZE:

  • Если BUFFER_SIZE = 124, то таких символов 132
  • Если BUFFER_SIZE = 1024, то таких символов 1032
  • Если BUFFER_SIZE = 1, то таких символов 9

Странные вещи (наверное только для меня):

  • Самое странное, что когда я использую точки останова и просматриваю receivedString, строки из stringToSend там нет, НО она появится в консоли.
  • Когда я отправляю данные через Termite (консоль для отправки данных через последовательную связь), проблема исчезает, и после этого я получаю ТОЛЬКО правильную строку даже с моей консолью.

Консольное приложение Windows:

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdint.h>
#include <tchar.h>
#include <windows.h>

#define BUFFER_SIZE 124

// This is code from microsoft website which I just transfered into function
HANDLE SerialCommSetup(TCHAR* comName, DWORD baudRate, BYTE byteSize, BYTE parity, BYTE stopBits )
    {
    DCB dcb;
    BOOL fSuccess;
    TCHAR* pcCommPort = comName; //  Most systems have a COM1 port
    COMMTIMEOUTS timeouts = { 0 };

    //  Open a handle to the specified com port.
    HANDLE hCom = CreateFile( pcCommPort,
                       GENERIC_READ | GENERIC_WRITE,
                       0,      //  must be opened with exclusive-access
                       NULL,   //  default security attributes
                       OPEN_EXISTING, //  must use OPEN_EXISTING
                       0,      //  not overlapped I/O
                       NULL ); //  hTemplate must be NULL for comm devices

    if ( hCom == INVALID_HANDLE_VALUE )
        {
        printf( "CreateFile failed with error %d.\n", GetLastError() );
        return hCom;
        }

//  Initialize the DCB structure.
    SecureZeroMemory( &dcb, sizeof( DCB ) );
    dcb.DCBlength = sizeof( DCB );

//  Build on the current configuration by first retrieving all current settings.
    fSuccess = GetCommState( hCom, &dcb );

    if ( !fSuccess )
        {
        printf( "GetCommState failed with error %d.\n", GetLastError() );
        return hCom;
        }

//  Fill in some DCB values and set the com state: 
    dcb.BaudRate = baudRate;    //  baud rate
    dcb.ByteSize = byteSize;    //  data size, xmit and rcv
    dcb.Parity = parity;        //  parity bit
    dcb.StopBits = stopBits;    //  stop bit

    fSuccess = SetCommState( hCom, &dcb );

    if ( !fSuccess )
        {
        printf( "SetCommState failed with error %d.\n", GetLastError() );
        return hCom;
        }

    timeouts.ReadIntervalTimeout = 50;
    timeouts.ReadTotalTimeoutConstant = 50;
    timeouts.ReadTotalTimeoutMultiplier = 10;
    timeouts.WriteTotalTimeoutConstant = 50;
    timeouts.WriteTotalTimeoutMultiplier = 10;

    if ( SetCommTimeouts( hCom, &timeouts ) == 0 )
        {
        fprintf( stderr, "Error setting timeouts\n" );
        CloseHandle( hCom );
        return hCom;
        }

//  Get the comm config again.
    fSuccess = GetCommState( hCom, &dcb );

    if ( !fSuccess )
        {
        printf( "GetCommState failed with error %d.\n", GetLastError() );
        return hCom;
        }

    _tprintf( TEXT( "\nSerial port %s successfully reconfigured.\n" ), pcCommPort );
    return hCom;
    }

int main( void )
{
    char stringToSend[] = "Sended test string!\0";
    char receivedString[BUFFER_SIZE];
    TCHAR wroteCom[5];  // Variable for name of port where the MCU is connected (find it in device manager or avrdude)

// Enter on which port you have connected the MCU to your PC (COM0 - COM9) - mine is on COM3
    printf( "Enter COM name: " ); 
    if ( !scanf( "%ls", wroteCom ) )
        return -1;

    if ( !wroteCom )
        return -2;

    wroteCom[4] = '\0';

/* Serial communication inicialization with parameters:
*   - Port:         (which you typed)
*   - Baud rate:    9600
*   - Data bits:    8
*   - Parity:       NONE
*   - Stop bits:    1 
*/
    HANDLE mainCom = SerialCommSetup( wroteCom, CBR_9600, 8, NOPARITY, 1 );
    if ( !mainCom )
        return 1;

// Send string to MCU
    if ( !WriteFile( mainCom, stringToSend, sizeof( stringToSend ), NULL, NULL ) )
        {
        CloseHandle( mainCom );
        return 2;
        }

// Receive string from MCU and write it into console
    if ( !ReadFile( mainCom, receivedString, sizeof( stringToSend ), NULL, NULL ) )
        {
        CloseHandle( mainCom );
        return 3;
        }   
    printf( "\nYou sent: \n\n%s\n\n", receivedString );
    

    CloseHandle( mainCom );
    return 0;
}

Встроенное приложение в MCU:

#define F_CPU 16000000 // Set the clock speed
#define BAUD_RATE 9600 // Set the baud rate
#define MYUBRR F_CPU/16/BAUD_RATE-1 // Calculate the value for UBRR0 - from datasheet

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

unsigned int newString = 0;
unsigned int index = 0;
char receivedString[1024];

// USART init - from datasheet
void USART_Init( unsigned int ubrr)
{
    /* Set baud rate */
    UBRR0H = (unsigned char)(ubrr>>8);
    UBRR0L = (unsigned char)ubrr;
    /* Enable receiver and transmitter */
    UCSR0B = (1<<RXEN0)|(1<<TXEN0);
    /* Set frame format: 8data, 1stop bit */
    UCSR0C = (1<<UCSZ01)|(1<<UCSZ00);
} // USART_Init

// USART receiving - from datasheet
unsigned char USART_Receive( void )
{
    /* Wait for data to be received */
    while ( !(UCSR0A & (1<<RXC0)) )
    ;
    /* Get and return received data from buffer */
    return UDR0;
}

// USART sending - from datasheet
void USART_Transmit( unsigned char data )
{
    /* Wait for empty transmit buffer */
    while ( !( UCSR0A & (1<<UDRE0)) )
    ;
    /* Put data into buffer, sends the data */
    UDR0 = data;
}

void SendString(char* stringToSend)
{
    for(unsigned int index = 0; stringToSend[index] != '\0'; index++)
        USART_Transmit(stringToSend[index]);
    
    USART_Transmit('\0');
}

ISR(USART0_RX_vect) //
{
    char c = USART_Receive();
    
    if ( c == '\n' || c == '\0'|| index >= 1023)
        {
        receivedString[index] = '\0';
        index = 0;
        newString = 1; // Indication of receiving whole string
        }
    else
        receivedString[index++] = c;
}


int main(void)
{
    
    USART_Init(MYUBRR);
    
    sei(); // Enable interrupts
    
    UCSR0B |= (1<<RXCIE0); // Enable complete interrupt
    
    while (1) 
        {
        if (newString == 1)
            {
            newString = 0;
            SendString(receivedString);
            }
        }
}

Вопросы:

  • Должен ли я установить какой-то бит/флаг в MCU, который вызывает такое поведение?
  • Может проблема в формате строки?
  • Я просто полностью упустил что-то, что я должен делать до/после отправки данных с ПК/MCU?

Что я пробовал:

  • Я пытался найти какое-то решение в Интернете, но ничего не могу найти
  • В качестве эксперимента я ничего не пробовал, потому что, честно говоря, я совершенно не понимаю, в чем может быть проблема.

Я был бы очень признателен за любую помощь, даже с рефакторингом/другими советами по кодированию.


Ответы на комментарии:

  1. Не могли бы вы напечатать значения в шестнадцатеричном виде и добавить количество этих элементов?

Выход:

ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc >ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc >ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc >ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc >ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc 53 65 6e 64 65 64 20 74 65 73 74 20 73 74 72 69 6e 67 21

Количество этих символов всегда равно BUFFER_SIZE + 8, а десятичный символ представлен как -52


Решение:

  • Я уже добавил 4-й параметр в функции WriteFile() и ReadFile(), и проблемы там не было (но это определенно может вызвать некоторые проблемы, потому что в документации сказано, что последние два параметра не могут быть оба NULL).
  • Где-то в документации я нашел, что ReadFile() всегда должно наступать после того, как происходит событие, и функция WaitCommEvent() его перехватывает. Тип события задается SetCommMask(mainCom, EV_RXCHAR), поэтому оно происходит, когда во входном буфере есть символ (есть документация по функции SetCommMask() ). Это решение показывает, что ошибка вызвана платой, которая не отправляет данные, потому что WaitCommEvent() попадет в бесконечный цикл ожидания получения данных. Однако без использования WaitCommEvent() кажется, что отправленные данные хранятся в какой-то памяти и считываются оттуда, поэтому, даже когда плата их не отправляет, они печатаются (я действительно не понимаю, почему, но см. @thebusybee ответ). Что на самом деле устраняет проблему, так это простое повторное подключение платы каждый раз, когда я загружаю Windows (после повторного подключения все работает даже с использованием WaitCommEvent()), что приводит меня к совершенно другому вопросу, если Windows отправляет какие-то странные вещи в USB-порты при загрузке, поэтому это мешает доска, но это для другой темы. Это также может быть вызвано тем, что Windows использует разные параметры связи (например, скорость передачи данных и т. д.) во время загрузки. Но это для другого/другого вопроса.

У вас есть два символа новой строки в строке формата для вывода: "\nYou sent: \n\n%s\n\n". Это вставляет пустую строку.

the busybee 08.01.2023 12:46

@thebusybee Итак, я отредактировал вопрос и уточнил, что моя проблема в том, что это перед правильной строкой, а не пустые строки.

Wartiik 08.01.2023 13:26

Замечу, что receivedString объявлен со 124 элементами, но в слоте in, представленном соответствующим %s в выходном формате, печатается более 124 символов. Однако я не вижу причин, по которым представленный вызов ReadFile() будет читать столько байтов. Возможно, стоит использовать четвертый параметр ReadFile(), чтобы получить собственное представление этой функции о том, сколько байтов она прочитала.

John Bollinger 08.01.2023 14:32

Постоянно ли количество символов от запуска к запуску?

John Bollinger 08.01.2023 14:42

Я не знаю, важно ли это, но ваш массив stringToSend заканчивается двумя нулевыми символами (один явный в инициализаторе и один неявный). Вы записываете оба в последовательный порт и разрешаете чтение обоих обратно. Однако код MCU ожидает, что только один нулевой символ или символ новой строки завершает сообщение, и он отправит обратно только один (для каждого сообщения).

John Bollinger 08.01.2023 14:53

@JohnBollinger Итак, я быстро проверил, сколько там символов, и да, оно постоянно, но если я изменю BUFFER_SIZE, то их появится больше, поэтому оно увеличивается с BUFFER_SIZE. Для информации, если BUFFER_SIZE = 124, то этих символов 132. Когда я увеличиваю значение до BUFFER_SIZE = 1024, таких символов становится 1032. Если я изменю stringToSend[] = "A\0" и уменьшу BUFFER_SIZE = 1, то таких символов будет 9.

Wartiik 08.01.2023 14:55

Тогда это BUFFER_SIZE + 8. Интересный. Это очень говорит о том, что проблема на стороне Windows, потому что размер буфера никогда не передается на MCU.

John Bollinger 08.01.2023 14:57

Я не думаю, что в этом случае это необходимо, но что, если вы объявите receivedString с инициализатором? char receivedString[BUFFER_SIZE] = {0};

John Bollinger 08.01.2023 15:03

Согласно документации, четвертый и пятый параметры ReadFile() не могут быть оба нулевыми.

John Bollinger 08.01.2023 15:09

Итак, если я инициализирую receivedString таким образом, он ничего не вернет, а в конце программы receivedString просто строка, полная \0

Wartiik 08.01.2023 15:11

Кажется, что происходит то, что данные, считанные ReadFile(), идут не в то место — через 8 байтов после конца буфера receivedString. Я не знаю, почему это так, но не пропустите мой предыдущий комментарий о параметрах ReadFile() — последние два не должны оба быть нулевыми. То же самое относится и к WriteFile().

John Bollinger 08.01.2023 15:13

Возможно, вам придется отредактировать заголовок, потому что он явно спрашивает об этой пустой строке и ничего не говорит о реальной проблеме.

the busybee 09.01.2023 09:31

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

the busybee 09.01.2023 17:37

@thebusybee Не могли бы вы сказать мне, как бы вы назвали эту проблему? Честно говоря, я думал так же, как «Что я искал, чтобы найти решение, прежде чем задать вопрос здесь», и все. У меня нет проблем с редактированием, просто я действительно не знаю, как это лучше назвать.

Wartiik 09.01.2023 21:31

Что вы думаете о «Почему я получаю символы со смещением в буфере при использовании ReadFile()?» а теги для winapi и серийной связи?

the busybee 09.01.2023 21:36

Звучит хорошо для меня, поэтому я изменил его, надеюсь, теперь все в порядке. Спасибо, что помогли мне с этим

Wartiik 09.01.2023 21:42
receivedString, sizeof( stringToSend ) - ошибка копирования/вставки? Должно быть sizeof( receivedString ). Вы также не проверили, сколько байтов было прочитано. Я подозреваю, что было прочитано ноль байтов, поэтому вы печатаете неинициализированные данные.
Raymond Chen 09.01.2023 21:43

@RaymondChen Этот параметр функции указывает максимально возможное количество байтов, которые будут прочитаны, поэтому все в порядке, потому что в этом примере я пытаюсь отправить строку, поэтому я не могу прочитать больше байтов, чем я отправил.

Wartiik 09.01.2023 21:47

Почему, @RaymondChen? Программа действительно ожидает получить sizeof( stringToSend ) байт. Если бы stringToSend было больше, чем receivedString, то это было бы проблемой, но это не так.

John Bollinger 09.01.2023 21:47

Другой эксперимент: вместо того, чтобы печатать полученные символы в виде одной строки, не могли бы вы вывести каждый из них в шестнадцатеричном виде? А количество полученных символов? Было бы полезно увидеть байты, которые отображаются здесь как .

the busybee 09.01.2023 22:28

Вам повезло, что sizeof(stringToSend) меньше BUFFER_SIZE. Если вы действительно намеревались получить только sizeof(stringToSend) байт, то почему размер receivedString не связан с sizeof(stringToSend)? Когда-нибудь вы сделаете stringToSend больше, и тогда receivedString станет недостаточно большим, и вы получите переполнение буфера и CVE.

Raymond Chen 09.01.2023 23:17

@RaymondChen О да, ты определенно прав. Я не понимал, что на самом деле я создаю строку только для 124 символов, поэтому она может очень легко переполниться. Спасибо за это.

Wartiik 10.01.2023 00:05
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
22
145
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

ReadFile() не считывает ни одного байта с последовательного устройства из-за вашего ошибочного вызова с обоими последними параметрами, установленными на NULL. Однако ReadFile() не вернулся FALSE.

Значения 0xCC (распечатанные с расширенным знаком как 0xFFFFFFCC), которые заполняются в receivedString, используются в качестве помощи при отладке.

Компилятор вашего ПК, по-видимому, находит переменную receivedString перед переменной stringToSend в стеке. Дополнительные байты, скорее всего, являются канареечными словами.

Теперь, когда вы печатаете receivedString, функция printf сканирует память, пока не найдет завершающую '\0'. Это происходит после того, как содержимое stringToSend было дополнительно отсканировано и скопировано на вывод. Итак, то, что вы видите, получено не ReadFile(), а существующими персонажами stringToSend, и это именно то, что вы ожидаете получить.

Это визуализирует ситуацию с памятью:

(нижний адрес)... receivedString набивка stringToSend ...(более высокий адрес) 0xCC, 0xCC, ... 0xCC 0xCC, ... 0xCC 'С', 'е', ... '!', '\0'

Что вы должны сделать?

  1. Никогда не игнорируйте возвращаемые и выходные значения. В этом случае проверьте количество фактически прочитанных байтов.
  2. Всегда читайте документацию функции и используйте ее соответствующим образом. Обычно, и я боюсь это говорить, особенно в Windows, действует принцип «мусор на входе, мусор на выходе».

Другие вопросы по теме