[Из песочницы] Modbus на российском микроконтроллере К1986ВЕ92QI
Попал мне в руки российский микроконтроллер К1986ВЕ92QI производства АО «ПКК Миландр» с 32-битным RISC ядром ARM Cortex-M3 128кБ Flash и 32кБ ОЗУ, сразу же захотелось изучить и опробовать его в действии.
Микроконтроллер поставляется в упаковке, которой позавидуют китайцы с AliExpress. Микросхема лежит в кассете из толстой алюминиевой фольги, которая обернута фольгированной бумагой, проложено поролоном, и весь этот «бутерброд» в картонной коробке с внутренними стенками покрытыми фольгой. Вообщем защита от статического электричества на высоте.
К микроконтроллеру идет этикетка и протокол отбора продукции, что очень даже приятно.
Для начала нужно было разработать принципиальную схему отладочной платы и определится с компонентами. Остановился на минимуме компонентов: стабилизатор на 3.3в для питания от USB порта, кварцевый резонатор на 8МГц, разъем miniUSB, кнопка сброса, подтягивающие резисторы и sip разъемы. Думаю для начальных опытов с микроконтроллером хватит. Также установил smd переключатель для выбора режима встроенного загрузчика. Микроконтроллер позволяет выбирать метод загрузки программы по одному из двух последовательных интерфейсов UART или JTAG/SWD, при этом JTAG позволяет производить отладку программы в микроконтроллере. Выбор метода загрузки программы определяется логическими уровнями на выходах PF4, PF5, PF6. Все возможные варианты представлены в таблице:
Микроконтроллер представляет из себя микросхему выполненную в пластиковом корпусе LQFP64 с выводами шириной 0.3 мм и с расстоянием между ними 0.2 мм, что навело на мысль о невозможности создать печатную плату приемлемого качества по технологии ЛУТ, но опыт подтвердил обратное. За пару часов был сделан чертеж печатной платы в Sprint Layout, распечатан на бумаге повышенной плотности Lamond и перенесен на стеклотекстолит. Травление происходило в растворе перекиси и лимонной кислоты на глазок и заняло около часа, на удивление качество проводников оказалось приемлемым с первого раза, что порадовало.
И так плата создана, все компоненты распаяны, остается запрограммировать. Будем использовать среду для разработки от 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 и «О чудо!» все заработало, в микроконтроллер прошилась тестовая программа.
На этом радость закончилась, так как после прошивки микроконтроллер хоть и работал, но отладчиком видится перестал. Видимо после перепрошивки линии порта JTAG-A переопределяются на другое функциональное назначение, хотя в программе порт B на котором находится JTAG-A даже не инициализировался. Разбираться в этом не хотелось, так как есть еще и JTAG-B. При подключении к альтернативному JTAG интерфейсу, все заработало как часы. В дальнейшем его и будем использовать для программирования и отладки.
Первой же поставленной себе задачей, было подключить контроллер к SCADA системе используя протокол Modbus. Дабы не изобретать велосипед возьмем кроссплатформенную свободно распространяемую библиотеку Freemodbus, и портируем ее для нашего микроконтроллера.
Для создания проектов в Keil на микроконтроллере от Миландр нужно для начала установить software pack. Делается это простым двойным кликом мыши по файлу. Далее Keil все сделает сама.
И так создаём новый проект. Выбираем наш микроконтроллер и компоненты библиотеки которые нам понадобятся:
В дереве проекта создаем группу Modbus Slave и добавляем туда следующие файлы из библиотеки Freemodbus:
И не забываем в опциях проекта указать компилятору следующие пути к каталогам библиотеки.
Теперь можно приступить конкретно к портированию библиотеки 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 ();» и все прекрасно начинает работать, пакеты бегают, регистры читаются, а дальше дело техники.