КОТ++
Прикладное программирование
Системное программирование
Программирование микроконтроллеров

Программирование — это не наука, а ремесло. (c) Ричард Столлман

Поиск по тегам

Опубликовано
Комментарии 2

Так как я в процессе программирования больше всего ценю сложность задач, то решил ознакомиться с программированием микроконтроллеров. Считаю, что программирование мк довольно не тривиальная задача и есть над чем поразмыслить. Да и не всю же жизнь знать только одну лишь Arduino. Начинаем вылезать из ардуиновских штанов. Я хотел начать с stm32, но всё-таки решил, что хочу попробовать PIC для начала. Скачал MPLAB IDE X, хочу сказать, что это очень удобная среда разработки, тем более после Arduino IDE уж точно. Далее устанавливаю компилятор XC8, вот тут я немного подгорел, что компилятор не включён в среду разработки, или же хотя бы нет скачки и установки прям через саму MPLAB IDE. Но не страшно, установка достаточно простая.

Далее я создаю проект, выбираю контроллер PIC18F4550, выбор обусловлен тем, что пока я смотрел в гугле информацию по PIC, то часто попадались примеры и туториалы именно по этой модели контроллера. Настало время поставить перед собой задачу и решить её. На мой взгляд очень интересная и полезная задача (возможно потом использую в дальнейших проектах) - это реализация некоторого протокола для связи с ПК по COM порту. То есть в программе нужно реализовать связь по UART в рамках некоторого протокола.

Протокол, который я буду реализовывать: Первый байт пакета - длина пакета. Второй байт пакета - номер пакета. Третий байт - команда. Остальные байты - какие-то данные. Последние два байта - контрольная сумма пакета, буду использовать CRC16. Пакеты должны следовать строго по нумерации, нумерацию будем считать одним байтом, после 0го пакета должен следовать 1ый, 1го - 2ой ... 254го - 255ый, 255го - 0ой. Пакеты, которые ПК пришлёт вне нумерации будут отброшены. Пакеты с неверной контрольной суммой тоже будут отброшены. Контроллер не подтверждает приём каждого пакета, заместо этого он периодически шлёт пакет, в котором содержится информация с номером последнего принятого пакета.

А теперь время приступить к написанию кода. Начать я решил с того, чтобы разобраться как работать с UART в своей программе. Оказалось, что есть два варианта: через библиотеки или напрямую работая с определёнными регистрами. Библиотечные методы довольно просты, я решил ознакомиться с ними, но потребовалось дополнительно установить библиотеку "Peripheral Library", но вот сколько я не мучался настроить работать компилятор XC8 с ней - мне не удалось. Было решено напрямую управлять регистрами из программы для работы с UART. Реализовал по примеру из инета, теперь нужно как-то это всё проверить.

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

Первый: есть замечательная программа PIC18 Simulator IDE от разработчика OshonSoft, она позволяет симулировать работу мк PIC 18-ой серии. К слову, на сайте присутствуют и симуляторы под PIC других серий, а также симулятор AVR. Также у них есть программа для мониторинга последовательного порта, отличная штука, до этого мне попадались мониторы COM порта отвратительного качества, с глюками, либо же платные, которые и попадаются в гугле. Так что Serial Port Monitor от OshonSoft очень годная штука и дальше ещё может пригодиться.

Работать в данном симуляторе достаточно просто, выбираем частоту, нужную модель контроллера, загружаем прошивку в виде .hex файла, выбираем нужный темп (скорость) симуляции, стартуем симуляцию. Программа имеет широкий спектр инструментов, которые представлены на скрине. Нам же сейчас нужен инструмент для проверки работоспособности UART. Для этого есть Hardware UART Simulation Interface и Software UART Simulation Interface. В Hardware interface всё заработало отлично, то есть видно что приходят байты нам в качестве ответа. А вот Software interface у меня так и не завёлся, к слову я считаю что это какой-то баг. В любом случае, эта программа не позволяет работать с мк через виртуальный COM порт, поэтому не смотря на многочисленные достоинства этой программы, она на данный момент не подходит.

Второй способ: симуляция работы мк в Proteus. Этот способ и придётся использовать, так как для работы с мк хочется иметь в системе COM порт, по которому можно послать данные контроллеру, или получить данные от контроллера, а Proteus это точно умеет. С Proteus я особо никогда не работал, поэтому не особо хотелось с ним знакомиться, но надо, так надо. Далее расскажу как создать виртуальный COM порт в системе и соединиться через него с симулированным мк в Proteus.

Кидаем на рабочую область нужный нам микроконтроллер, кидаем компонент COMPIM, соединяем TX с TX, RX с RX. Указываем в настройках микроконтроллера в Proteus нужную частоту и .hex файл. Далее нам нужно создать и соединить между собой два виртуальный последовательных порта. С этим нам поможет программа com0com. В ней ничего сложного, там уже есть созданные порты, выглядеть должно как на скрине. Теперь один COM порт прописываем в настройках COMPIM в Proteus, а другой можно использовать либо в своей собственной программе либо в уже готовой терминальной программе (например Putty), главное выставить верный baudrate.

Но тут не всё так просто. Потому что моя программа для PIC наотрез отказывалась верно работать в Proteus. Сначала я винил скрытые ошибки в моём коде, поэтому раз за разом пробовал всё новые примеры кода для работы с UART из гугла. Когда уже стало понятно, что в коде ошибки точно нет, то закралась мысль, что Proteus при настройке тактовой частоты в 4МГц и baudrate в 9600 что-то не так моделирует, может что-то не успевает и где-то обсчитывается. Я понизил тактовую частоту до 1МГц и baudrate до 300 - и заработало! Какое-то время я винил косяки в симуляции Proteus, но на форуме добрые люди подсказали, что при моих изначальных параметрах ошибка UART достигала 6.99% и отсюда следовали все проблемы. По даташиту выбрал другие параметры, чтобы и 9600 и 4МГц, и в итоге прошивка отлично заработала! Так как во всех туториалах, которые я прочитал по UART на PIC не было сказано про эту проблему ни слова, а промучался я с этим несколько часов, то хочу этим текстом обратить внимание на это новичков. На одном скрине параметры, которые портят работу с UART, на другом уже с ошибкой всего 0.16%, с которой можно хорошо жить.

А вот код программы:

#define _XTAL_FREQ 4000000

#include <xc.h>
#include <stdint.h>
#include <stdbool.h>

#include "fastcrc.h"
#include "commands.h"

struct Packet
{
    uint8_t length;
    uint_least8_t data[100];
};

uint8_t lastNumPacket;

void initUART(void);
void writeUART(unsigned char data);

bool dataAvailable(void);
bool overrunError(void);

void analyzePacket(struct Packet* packet);
bool checkCheckSum(struct Packet* packet);
bool checkCorrectNumPacket(uint8_t numPacket);

void main(void) 
{     
    initUART();

    uint8_t counterBytes = 0;
    lastNumPacket = 255;
    struct Packet packet;

    while (1)
    {
        if (dataAvailable() == true)
        {
            if (overrunError() == true)
            {           
                CREN = 0;
                NOP();
                CREN = 1;
            }
            if (counterBytes == 0)
            {
                packet.data[0] = RCREG;
                packet.length = packet.data[0];
                counterBytes++;
            }
            else 
            {
                packet.data[counterBytes] = RCREG;
                counterBytes++;
                if (counterBytes == packet.length)
                {
                    counterBytes = 0;
                    if (checkCorrectNumPacket(packet.data[1]) == true && checkCheckSum(&packet) == true)
                    {
                        lastNumPacket++;
                        writeUART(0x33);
                        analyzePacket(&packet);
                    }
                }
            }
        }
    }
    return;
}

void initUART()
{
    TXSTA = 0; //Reset USART registers to POR state
    RCSTA = 0;

    TRISC6 = 0; //TX as output
    TRISC7 = 1; //RX as input
    SYNC = 0; //Async operation
    TX9 = 0;  //No tx of 9th bit
    RX9 = 0;  //No rx of 9th bit
    TXEN = 1;  //Enable transmitter
    CREN = 1; //Enable receiver
    SPEN = 1; //Enable serial port

    TXIE=0; //disable tx interrupts
    RCIE=0; //disbale rx interrupts 

    BRG16 = 0; //Divisor at 8 bit
    BRGH = 1; //No high-speed baudrate
    SPBRG = 25;  //9600 baudrate

    RCIF = 0;
}

void writeUART(unsigned char data)
{
  while(!TRMT);
  TXREG = data;
}

bool dataAvailable()
{
    if (RCIF != 0)
    {
        return true;
    }
    else
    {
        return false;
    }
}

bool overrunError()
{
    if (RCSTAbits.OERR)
    {
        return true;
    }
    else
    {
        return false;
    }
}

void analyzePacket(struct Packet* packet)
{
    uint8_t command = packet->data[2];
    switch (command)
    {
        case COMMAND_1: break;
    }
}

bool checkCorrectNumPacket(uint8_t numPacket)
{
    uint8_t ifReceivedLastNumPacket = lastNumPacket + 1;
    if (ifReceivedLastNumPacket == numPacket)
    { 
        return true;   
    }    
    else
    {
        return false;
    }
}

bool checkCheckSum(struct Packet* packet)
{
    uint16_t packetCrc = *((uint16_t*)(&packet->data + packet->length - 2));
    uint16_t calculatedCrc = fast_crc_blk(&(packet->data), packet->length - 2);
    if (calculatedCrc == packetCrc)
    {
        return true;
    }
    else
    {
        return false;
    }
}

Логика кода такая: инициализируем UART, далее в бесконечном цикле ждём байты, если принят байт, то считаем его за длину пакета, отсчитываем длину, далее проверяем соответствие нумерации и контрольную сумму (реализацию расчёта контрольной суммы я приводить тут не стал, так как тема статьи это не алгоритмы подсчёта контрольных сумм), если проверка прошла успешно, то принимаем пакет и выполняем команду из этого пакета.

Я намеренно реализовал без прерываний, руководствуясь тем, что если бы использовались прерывания, то при большом количестве пакетов программа бы застряла бы в этих прерываниях обработки UART и совсем забыла бы про основную работу, которую должен выполнять контроллер. Поэтому на мой взгляд логично обрабатывать UART без прерываний.

Теги , ,
Автор