[Из песочницы] Modbus на российском микроконтроллере К1986ВЕ92QI

Попал мне в руки российский микроконтроллер К1986ВЕ92QI производства АО «ПКК Миландр» с 32-битным RISC ядром ARM Cortex-M3 128кБ Flash и 32кБ ОЗУ, сразу же захотелось изучить и опробовать его в действии.

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

0qufjpcjdcgtbhvw-0fnxyrmass.jpeg

efyp2bwavnh5hqejpdqiatxogz8.jpeg

К микроконтроллеру идет этикетка и протокол отбора продукции, что очень даже приятно.

xi_fui1ln8ovx9k0laosvk76pdm.jpeg

Для начала нужно было разработать принципиальную схему отладочной платы и определится с компонентами. Остановился на минимуме компонентов: стабилизатор на 3.3в для питания от USB порта, кварцевый резонатор на 8МГц, разъем miniUSB, кнопка сброса, подтягивающие резисторы и sip разъемы. Думаю для начальных опытов с микроконтроллером хватит. Также установил smd переключатель для выбора режима встроенного загрузчика. Микроконтроллер позволяет выбирать метод загрузки программы по одному из двух последовательных интерфейсов UART или JTAG/SWD, при этом JTAG позволяет производить отладку программы в микроконтроллере. Выбор метода загрузки программы определяется логическими уровнями на выходах PF4, PF5, PF6. Все возможные варианты представлены в таблице:

kymyenw4iasc8nygqi79xliwgby.png

Микроконтроллер представляет из себя микросхему выполненную в пластиковом корпусе LQFP64 с выводами шириной 0.3 мм и с расстоянием между ними 0.2 мм, что навело на мысль о невозможности создать печатную плату приемлемого качества по технологии ЛУТ, но опыт подтвердил обратное. За пару часов был сделан чертеж печатной платы в Sprint Layout, распечатан на бумаге повышенной плотности Lamond и перенесен на стеклотекстолит. Травление происходило в растворе перекиси и лимонной кислоты на глазок и заняло около часа, на удивление качество проводников оказалось приемлемым с первого раза, что порадовало.

hyzbvtnawxs-d5kfdff0swzqx_w.jpeg

И так плата создана, все компоненты распаяны, остается запрограммировать. Будем использовать среду для разработки от Keil — MDK ARM uVision 5.0, к ней производителем микроконтроллера распространяется библиотека Standard Peripherals Library + software pack. По UART программировать не хотелось, поэтому решил использовать внутрисхемный программатор/отладчик ST-Link v2, а точнее его клон от неизвестного китайского производителя. Keil его поддерживает «из коробки», а вот микроконтроллер, хоть и в документации сказано, что поддерживает SWD интерфейс, но как и куда что подключать упомянуть забыли. После поисков в интернете по запросу: «JTAG — SWD переходник» было выяснено, что линия SWDIO подключается к JTAG-TMS, а SWCLK к JTAG-TCK и «О чудо!» все заработало, в микроконтроллер прошилась тестовая программа.

3pcaqm5rotui8hxuc8gi1svzrdi.jpeg

На этом радость закончилась, так как после прошивки микроконтроллер хоть и работал, но отладчиком видится перестал. Видимо после перепрошивки линии порта JTAG-A переопределяются на другое функциональное назначение, хотя в программе порт B на котором находится JTAG-A даже не инициализировался. Разбираться в этом не хотелось, так как есть еще и JTAG-B. При подключении к альтернативному JTAG интерфейсу, все заработало как часы. В дальнейшем его и будем использовать для программирования и отладки.

Первой же поставленной себе задачей, было подключить контроллер к SCADA системе используя протокол Modbus. Дабы не изобретать велосипед возьмем кроссплатформенную свободно распространяемую библиотеку Freemodbus, и портируем ее для нашего микроконтроллера.

Для создания проектов в Keil на микроконтроллере от Миландр нужно для начала установить software pack. Делается это простым двойным кликом мыши по файлу. Далее Keil все сделает сама.

И так создаём новый проект. Выбираем наш микроконтроллер и компоненты библиотеки которые нам понадобятся:

gjvj5hrhozennt2gtoebvuexdlw.jpeg

В дереве проекта создаем группу Modbus Slave и добавляем туда следующие файлы из библиотеки Freemodbus:

nlrtv4psj1ta43rupd9t_hco590.jpeg

И не забываем в опциях проекта указать компилятору следующие пути к каталогам библиотеки.

imxubxvmei8hr9zsqbpneut0bzk.jpeg

Теперь можно приступить конкретно к портированию библиотеки Freemodbus под наш микроконтроллер с использованием Standard Peripherals Library. Для этого нужно в файле portserial.c прописать функцию инициализации порта UART xMBPortSerialInit

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{

        // Включение тактирования порта F
        RST_CLK_PCLKcmd(RST_CLK_PCLK_PORTF, ENABLE); 
        // Объявление структуры для инициализации порта
        PORT_InitTypeDef uart2_port_set;  
        // Инициализация порта F для функции UART
        // Настройка порта по умолчанию
        PORT_StructInit(&uart2_port_set);  
        // Переопределение функции порта
        uart2_port_set.PORT_FUNC = PORT_FUNC_OVERRID;  
        // Установка короткого фронта
        uart2_port_set.PORT_SPEED = PORT_SPEED_MAXFAST;  
        // Цифровой режим работы вывода
        uart2_port_set.PORT_MODE = PORT_MODE_DIGITAL;  
        // Инициализация вывода PF1 как UART_TX (передача)
        uart2_port_set.PORT_Pin = PORT_Pin_1;
        uart2_port_set.PORT_OE = PORT_OE_OUT;
        PORT_Init(MDR_PORTF, &uart2_port_set);
        // Инициализация вывода PF0 как UART_RX (прием)
        uart2_port_set.PORT_Pin = PORT_Pin_0;
        uart2_port_set.PORT_OE = PORT_OE_IN;

        // Процедура инициализации контроллера UART
        // Включение тактирования UART2
        RST_CLK_PCLKcmd(RST_CLK_PCLK_UART2, ENABLE);
        // Объявление структуры для инициализации контроллера UART
        UART_InitTypeDef UART_InitStructure;
        // Делитель тактовой частоты UART = 1
        UART_BRGInit(MDR_UART2,UART_HCLKdiv1);
        // Конфигурация UART
        // Скорость передачи данных – 115200 бод
        UART_InitStructure.UART_BaudRate = ulBaudRate;
        // Количество бит в посылке – 8
        UART_InitStructure.UART_WordLength = UART_WordLength8b;
        // Один стоп-бит
        UART_InitStructure.UART_StopBits = UART_StopBits1;
        // Без проверки четности
        UART_InitStructure.UART_Parity = UART_Parity_No;
        // Выключить работу буфера FIFO приемника и передатчика,
        // т.е. передача осуществляется по одному байту
        UART_InitStructure.UART_FIFOMode = UART_FIFO_OFF;
        // Разрешить прием и передачу данных
        UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_RXE | UART_HardwareFlowControl_TXE;
        // Инициализация UART2 с заданными параметрами
        UART_Init(MDR_UART2, &UART_InitStructure);
        // Включить сконфигурированный UART
        UART_Cmd(MDR_UART2, ENABLE);

    return TRUE;
}

функцию записи и чтения:

BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
        //Отправляем байт
            UART_SendData(MDR_UART2,ucByte);

    return TRUE;
}

BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
            //Читаем байт
    *pucByte = (uint8_t) UART_ReceiveData(MDR_UART2);
    return TRUE;
}

обработчик прерываний UART

    void USART2_IRQHandler(void)
{

  /* Событие при приеме байта ---------------------------------------------------*/
  if((UART_GetITStatus(MDR_UART2,UART_IT_RX)) != RESET)
  { 
         prvvUARTRxISR(  ); 
  }

  /* Событие при передаче байта ------------------------------------------------*/
  if((UART_GetITStatus(MDR_UART2,UART_IT_TX)) !=RESET)
  {
    prvvUARTTxReadyISR(  );
  } 
}

Следом за этим правим файл portimer.c в котором настраивается таймер который формирует временные отчеты для отслеживания конца пакета протокола modbus.

BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
        MDR_RST_CLK->PER_CLOCK |= (1<<14); // Включение тактирования TIM1
        MDR_RST_CLK->TIM_CLOCK = 0x0;
        MDR_RST_CLK->TIM_CLOCK |= (1<<24); // TIM1_CLK_EN
        MDR_RST_CLK->TIM_CLOCK |= 0x07; // HCLK/8 выбор частоты

        MDR_TIMER1->CNTRL = 0x00000002; //Запись регистра управления
        MDR_TIMER1->CNT = 0x00000000; //Обнуление регистра
        MDR_TIMER1->PSG = 0x2; //f/1 выбор предделителя
        while((MDR_TIMER1->CNTRL & 0x004) != 0) {__NOP();} //ожидание конца записи делителя

        MDR_TIMER1->ARR = usTim1Timerout50us; // установка базы основного счетчика
        while((MDR_TIMER1->CNTRL & 0x004) != 0) {__NOP();} //ожидание записи базы основного счетчика

        MDR_TIMER1->IE = 0x00000002; //(CNT==ARR)->IE выбор действия для срабатывания прерывания
        NVIC->ISER[0] = (1<<14); // Global EN for IRQ14 разрешаем прерывание
        MDR_TIMER1->CNTRL |= (1<<0); //Timer1 ON включаем таймер
    return TRUE;

}

inline void
vMBPortTimersEnable(  )
{
    /* Разрешаем работу таймера */
        MDR_TIMER1->CNTRL |= (1<<0); //Timer1 ON
}

inline void
vMBPortTimersDisable(  )
{
    /* Запрещаем работу таймера */
        MDR_TIMER1->CNTRL &= ~(1<<0); //Timer1 OFF
}

static void prvvTIMERExpiredISR( void )
{
    ( void )pxMBPortCBTimerExpired(  );
}

void Timer1_IRQHandler(void)
{
    //Обработчик прерывания таймера
        MDR_TIMER1->STATUS &= ~0x002; //IE FLAG=0
     prvvTIMERExpiredISR( );
}

В main.c добавим функции обработки регистров modbus, неиспользуемые регистры заглушим заглушками

/* ----------------------- Defines ------------------------------------------*/
#define REG_INPUT_START 1000
#define REG_INPUT_NREGS 4

/* ----------------------- Static variables ---------------------------------*/
static USHORT   usRegInputStart = REG_INPUT_START;
static USHORT   usRegInputBuf[REG_INPUT_NREGS];

eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iRegIndex;

    if( ( usAddress >= REG_INPUT_START )
        && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
    {
        iRegIndex = ( int )( usAddress - usRegInputStart );
        while( usNRegs > 0 )
        {
            *pucRegBuffer++ =
                ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );
            *pucRegBuffer++ =
                ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );
            iRegIndex++;
            usNRegs--;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }

    return eStatus;
}

eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
                 eMBRegisterMode eMode )
{
    return MB_ENOREG;
}

eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
               eMBRegisterMode eMode )
{
    return MB_ENOREG;
}

eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    return MB_ENOREG;
}

На этом портирование закончено, осталось только в функции int main (void) инициализировать библиотеку и в цикле вызывать eMBPoll ();

int main (void) 
{

     eMBErrorCode    eStatus;
//Настройки UART не поддерживаются кроме скорости, необходимо в portserial.c в xMBPortSerialInit описать выбор режимов

    eStatus = eMBInit( MB_RTU, 0x0A, 0, 19200, MB_PAR_NONE ); 

        /* Enable the Modbus Protocol Stack. */
    eStatus = eMBEnable(  );

    while(1)
    {
    eStatus = eMBPoll(  );
        //обработчик ошибок
        if (eStatus!= MB_ENOREG){};

        /* Here we simply count the number of poll cycles. */
        usRegInputBuf[0]++;
    }

}

Компилируем все без ошибок, но ничего не работает. В режиме отладки узнаем, что пакеты принимаются обрабатываются и программа зависает на инициализации передачи. При включении прерывания от передатчика UART, прерывание не вызывается, и программа уходит в бесконечный цикл. После штудирования раздела «Описание работы UART» спецификации на микроконтроллер наткнулся на Примечание:


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

Ну что же не беда, ищем где происходит начало передачи, в файле mbrtu.c находим строчки кода

/* Activate the transmitter. */
        eSndState = STATE_TX_XMIT;
        vMBPortSerialEnable( FALSE, TRUE );

и принудительно отправляем байт в передатчик UART, для этого добавляем строку: «xMBRTUTransmitFSM ();» и все прекрасно начинает работать, пакеты бегают, регистры читаются, а дальше дело техники.

© Habrahabr.ru