Modbus on the Russian microcontroller K1986BE92QI

I got into the hands of a Russian microcontroller K1986BE92QI produced by PKK Milandr JSC with 32-bit RISC core ARM Cortex-M3 128kB Flash and 32kB RAM, I immediately wanted to study and test it in action.


The microcontroller comes in a package that the Chinese will envy with AliExpress. The microcircuit lies in a cassette made of thick aluminum foil, which is wrapped with foil paper, laid with foam rubber, and the whole “sandwich” in a cardboard box with inner walls covered with foil. In general, protection from static electricity at altitude.




A label and a product selection protocol go to the microcontroller, which is very pleasant.



To begin with, it was necessary to develop a schematic diagram of the debug board and decide on the components. I stopped at a minimum of components: a stabilizer at 3.3V for power supply from the USB port, a quartz resonator at 8 MHz, a miniUSB connector, a reset button, pull-up resistors and sip connectors. I think for the initial experiments with the microcontroller is enough. Also set the smd switch to select the integrated bootloader mode. The microcontroller allows you to select the program download method via one of two serial interfaces UART or JTAG / SWD, while JTAG allows you to debug the program in the microcontroller. The choice of the program loading method is determined by the logic levels at the outputs of PF4, PF5, PF6. All possible options are presented in the table:



The microcontroller is a microcircuit made in a LQFP64 plastic case with 0.3mm wide pins and with a spacing of 0.2mm between them, which made it impossible to create a printed circuit board of acceptable quality using LUT technology, but experience has confirmed the opposite. For a couple of hours, a PCB drawing was made in Sprint Layout, printed on Lamond high density paper and transferred to fiberglass laminate. Etching occurred in a solution of peroxide and citric acid by eye and took about an hour, surprisingly the quality of the conductors was acceptable the first time, which pleased us.



And so the board is created, all components are unsoldered, it remains to be programmed. We will use the development environment from Keil - MDK ARM uVision 5.0; the standard Peripherals Library + software pack is distributed by the microcontroller manufacturer to it.. I didn't want to program UART, so I decided to use the ST-Link v2 in-circuit programmer / debugger, or rather its clone from an unknown Chinese manufacturer. Keil supports it “out of the box”, but the microcontroller, although the documentation says that it supports the SWD interface, but forgot how to connect what to mention. After searching the Internet for the request: “JTAG - SWD adapter”, it was found that the SWDIO line is connected to JTAG-TMS, and SWCLK to JTAG-TCK and “Wonder!” Everything worked, the test program was flashed into the microcontroller.



This ended the joy, since after the firmware the microcontroller worked, although it seemed to stop the debugger. Apparently, after flashing the line of the JTAG-A port is redefined to another functional purpose, although in the program the port B on which the JTAG-A is located was not even initialized. I didn’t want to understand this, since there is also JTAG-B. When connected to an alternative JTAG interface, everything worked like a clock. Later we will use it for programming and debugging.


The first task set to itself was to connect the controller to the SCADA system using the Modbus protocol. In order not to reinvent the wheel, let's take the Freemodbus cross-platform free library and port it for our microcontroller.


To create projects in Keil on the microcontroller from Milandr, you first need to install the software pack. This is done by a simple double click on the file. Then Keil will do everything herself.


And so create a new project. Choose our microcontroller and library components that we need:



In the project tree, create a Modbus Slave group and add the following files from the Freemodbus library there:



And do not forget in the options of the project to indicate to the compiler the following paths to the library directories.



Now we can proceed specifically to porting the Freemodbus library under our microcontroller using the Standard Peripherals Library. To do this, in the file portserial.c set the initialization function of the port 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;
}

write and read function:


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 Interrupt Handler


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

Following this, we edit the portimer.c file in which a timer is configured which generates temporary reports to track the end of the modbus protocol packet.


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( );
}

In the main.c we add the modbus register processing functions, the unused registers are muffled with plugs


/* ----------------------- 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;
}

This completes the porting, it remains only in the function int main (void) to initialize the library and call eMBPoll () in a loop;


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]++;
    }
}

We compile everything without errors, but nothing works. In debug mode, we learn that packets are being processed and the program hangs on the transfer initialization. When the UART transmitter interrupt is enabled, the interrupt is not triggered, and the program goes into an infinite loop. After studying the “UART Job Description” section of the microcontroller's specification, I came across a Note:


Interruption of the transmitter works on the front, and not on the signal level. If the module and its interrupts are enabled before writing data to the transmitter's FIFO buffer, an interrupt is not generated. An interrupt occurs only when the FIFO buffer is empty.

Well, it does not matter, we are looking for where the transfer begins, in the mbrtu.c file we find the lines of code


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

and forcibly send a byte to the UART transmitter, to do this, add the line: "xMBRTUTransmitFSM ();" and everything starts to work fine, the packages run, the registers are read, and then the matter of technology.


Also popular now: