ATmega8 + Windows: связь через USB

разделы: AVR , UART , , дата: 19 августа 2015г.

С Windows у меня отношения не очень, но иногда приходится пользоваться, чтобы проверить какую-либо программу или железку. Или поиграть. Все барахло с этим связанное покоится на отдельном системнике, и я подумал, что неплохо было бы иметь игровой комп с внешним дисплеем температуры, загрузки ЦП и т.д. В этом посте речь коснется только реализации протокола связи между ATmega8 и Windows. Получение WMI данных будет рассмотрен позже.

Т.к. повторюсь, отношения у меня с этой OS не очень, покупать ее мне никогда даже в голову не приходило. Но однажды, один добрый самаритянин, подарил мне корпус системника с лицензионной наклейкой для Windows Vista Home Edition. Поэтому, все ниже написанное будет касаться именно этой версии Windows.

    Формат протокола:
  1. В сторону ATmega8 посылаются данные с контрольной суммой и разделителем. Микроконтроллер проверяет контрольную сумму и посылает на Windows ответ, совпала она или нет. После чего программа на Windows посылает новые данные. Т.е. такой полудуплексный(hulf-duplex) протокол.
  2. Максимальная длина строки 32 байта.
  3. В качестве разделителя выступает символ двоеточия ':'.
  4. Формат данных: данные + контрольный байт + ':'.
  5. Контрольный байт получается через XOR функцию.
  6. Простой поверочный пример: hellob: Здесь литера b является результатом XOR функции строки hello.

В качестве компилятора я использую MinGW, который идет в составе IDE Qt Creator. Несмотря на то, что я использую Qt Creator, предлагаемая ниже программа написана на обычном C++ и вероятно, может быть скомпилирована на MS Visual Studio C++. Исходник для Windows:

#include <tchar.h>
#include <windows.h>
#include <iostream>
#include <stdlib.h>
#include <string>

int serialport_write(HANDLE& port, std::string& str);
int serialport_read(HANDLE& port, std::string& str);

int main()
{
    LPCWSTR portname = L"COM3";
    DWORD  accessdirection =GENERIC_READ | GENERIC_WRITE;
    HANDLE m_hCommPort = CreateFile(portname, accessdirection,  0, 0,  OPEN_EXISTING,  0, 0);
    if (m_hCommPort == INVALID_HANDLE_VALUE)
    {
        std::cerr << "Error: " << GetLastError() << std::endl;
        return EXIT_FAILURE;
    }

    DCB dcb;
    memset(&dcb, 0, sizeof(dcb));
    dcb.DCBlength = sizeof(dcb);
    if (!BuildCommDCB(L"baud=9600 parity=N data=8 stop=1", &dcb))
    {
        std::cerr << "Error: " << GetLastError() << std::endl;
        return EXIT_FAILURE;
    }

    dcb.fOutxCtsFlow = FALSE;
    dcb.fOutxDsrFlow = FALSE;
    dcb.fDtrControl = DTR_CONTROL_DISABLE;
    dcb.fDsrSensitivity = FALSE;
    dcb.fOutX = FALSE;
    dcb.fInX = FALSE;
    dcb.fNull = FALSE;
    dcb.fRtsControl = RTS_CONTROL_DISABLE;
    dcb.fAbortOnError = FALSE;
    if (!SetCommState(m_hCommPort,&dcb))
    {
        std::cerr << "Error: " << GetLastError() << std::endl;
        return EXIT_FAILURE;
    }


    Sleep(17000);
    std::cout << "READY!" <<std::endl;

    int err=0; // attempts counter
        // main loop
        for (int j=0;j<10;j++)
        {
            std::string data="hello";
            if ( serialport_write(m_hCommPort, data) < 0 )
            {
                err++;
                std::cerr << "ERORR: could not setup connection to port: " << portname << std::endl;
                if (err >10)
                {
                    std::cerr << "ERORR: exceeded the maximum allowed number connection attempts!" << std::endl;
                    CloseHandle(m_hCommPort);
                    return EXIT_FAILURE;
                } else
                {
                    Sleep(1000);
                    continue;
                }
            }

           Sleep (100);
            serialport_read(m_hCommPort,data);
            if (!(data.at(0) == 'O' && data.at(1) == 'K'))
            {
                err++;
                std::cerr << "ERROR: failure transferring data!" << std::endl;
                if (err > 10)
                {
                    CloseHandle(m_hCommPort);
                    return EXIT_FAILURE;
                }
            }

            Sleep(1000);
        }


    CloseHandle(m_hCommPort);

    return 0;
}

int serialport_write(HANDLE& port, std::string& str)
{
    // calculate hash
    unsigned char hash=str.at(0);
    for (int i=1; (unsigned int)i<str.length();i++)  hash=hash ^ str.at(i);

    str.push_back(hash);
    str.push_back(':');

    DWORD dwBytesRead = 0;
    LPCVOID data =str.c_str();
    if ( !WriteFile(port, data, str.length(), &dwBytesRead, NULL))
    {
        std::cerr << "ERROR: failure transferring: " << GetLastError() << std::endl;
        return EXIT_FAILURE;
    } else
        std::cout << "was  transferred: " << str << " length: " << str.length() << std::endl;
    return EXIT_SUCCESS;
}

int serialport_read(HANDLE& port, std::string& buf)
{
    char b[1];
    buf.clear();

    do
    {
        DWORD dwByteRead=0;
        if( !ReadFile(port, b, 1, &dwByteRead, NULL))
        {
            std::cerr << "ERROR: read from port is failure!" <<std::endl;
            return EXIT_FAILURE;
        } else if ( dwByteRead == 0 )  // if empty buffer
        {
            Sleep(10);
        } else
        {
            buf.push_back(b[0]);
        }

    } while( b[0] != '\n' );

    std::cout << "was read " << buf.length() << " bytes: "<< buf <<std::endl;
    return EXIT_SUCCESS;
}

Данный исходник является адаптацией для Windows исходника на с++ из поста: "Arduino + Linux: связь с компьютером через USB"

Здесь я должен раскрыть смысл оператора Sleep(17000), .т.е. ожидание 17 сек. после инициализации последовательного порта. У меня ATmega8 вставлена в Arduino плату с чипом FT232RL, у которого сигнал DTR(сигнал начала связи) соединен с Reset микроконтроллера. Поэтому при установке соединения по UART на этой плате, микроконтроллер перезагружается, и нужно ждать пока он заработает.

Исходник для ATmega8:

/* for ATMEGA-8 echo_serial.c read string via UART with symbol ':' as End-Of-Line
and write this string in UART backward
site: http://countzero.weebly.com

compile:
avr-gcc -mmcu=atmega8 -Wall -Os -o echo_serial.elf echo_serial.c
avr-objcopy -O ihex echo_serial.elf echo_serial.hex
avrdude -patmega8 -carduino -P/dev/ttyUSB0 -b19200 -D -Uflash:w:./serial.hex:i
*/

#define F_CPU 16000000UL
#define LEN 32
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

char buffer[LEN];

register unsigned char IT asm("r16");
register unsigned char HSH asm("r17");
volatile unsigned char KDX;

volatile unsigned char done;
volatile unsigned char IDX;

inline void clearStr(char* str)
{
 for(IT=0;IT<LEN;IT++)
  str[IT]=0;
}

void writeSerial(char* str)
{
 IT=0; while (str[IT] != 0 && IT < LEN)
 {
                while(!(UCSRA&(1<<UDRE))){};
                UDR = str[IT];
  IT++;
        }
}

ISR(USART_RXC_vect)
{
 char bf= UDR;
 buffer[IDX]=bf;
 IDX++;

 if (bf == ':' || IDX >= LEN)
 {
  KDX=IDX;
  IDX=0;
  done=1;
 }

}

void blink13(uint8_t count)
{
 PORTB |= (1<<PB5);
 count =(count <<1);count--; //count=(count*2)-1;
 for (IT=0;IT<count;IT++)
 {
  _delay_ms(500);
  PORTB ^= (1<<PB5);
 };
};

int main(void)
{
 // USART init
        UBRRL=103; // 9600
        UCSRB=(1<<TXEN)|(1<<RXEN)|(1<<RXCIE);
        UCSRC=(1<<URSEL)|(3<<UCSZ0); // 8N1

        DDRB |= (1<<PB5); //  pinMode(13,OUTPUT);

 blink13(3); //ready indication
 IDX=0;
 done=0;
 sei();

 for (;;)
        {
  if (done)
  {
   PORTB |= (1<<PB5); KDX--;
   if (KDX > 0 && KDX < LEN && buffer[KDX] == ':')
   {
     KDX--; HSH=buffer[KDX];
     for(IT=0; IT<KDX; IT++) HSH ^=buffer[IT];
     if (HSH)
      writeSerial("ERROR!\n");              
else
      writeSerial("OK\n");
   } else
    writeSerial("WRONG!\n");

   clearStr(buffer);
   PORTB &= ~(1<<PB5);
   done=0;
  }
 }
        return 0;
}

Это немного модифицированный исходник из поста: "ATmega8: работа на Си с USART/UART через прерывание"

В начале работы микроконтроллер трижды моргнет, сигнализируя о том, что он уже перезагрузился и готов к работе. Скрин с работающей программой:

поделиться: