Содержание

Драйвер пинов, GPIO

Драйвер состоит из общего заголовочника MDR_GPIO.h и двух файлов с имплементацией функций:

  1. MDR_GPIO_VE8x.c - для 1986ВЕ8Т, 1923ВК014, "Электросила".
  2. MDR_GPIO.c - для остальных микроконтроллеров.

Через файл MDR_Config.h в драйвер подключается файл описания микроконтроллера, например, MDR_1986VE1.h. А уже в файле микроконтроллера подключается файл описания регистров, полей и масок - MDR_GPIO_VE8x_defs.h или MDR_GPIO_defs.h соответственно.

В MDR_1986ВЕ1.h:
  /*===============  GPIO Port ===================*/
  #include <MDR_GPIO_defs.h>
  #define   MDR_PORT_Type    MDR_PORT_Type__Ext

В MDR_1986ВЕ9x.h:  
  /*===============  GPIO Port ===================*/
  #include <MDR_GPIO_defs.h>
  #define   MDR_PORT_Type    MDR_PORT_Type__Base
  
В MDR_1986ВЕ8.h:
  /*===============  GPIO Port ===============*/
  #include <MDR_GPIO_VE8x_defs.h>

В файле MDR_GPIO_defs.h определено две структуры регистров основная и расширенная, в расширенной есть регистры SET и CLR, которые есть не во всех микроконтроллерах. Поэтому выбор структуры сделан прямо в заголовочном файле микроконтроллера.

Пины как и раньше задаются масками, (для 1986ВЕ8Т и подобных на порт приходится 32 вывода):

  #define MDR_Pin_0                  0x00000001UL  /*!< Pin 0 selected */
  #define MDR_Pin_1                  0x00000002UL  /*!< Pin 1 selected */
  ...
  #define MDR_Pin_15                 0x00008000UL  /*!< Pin 15 selected */
  
#if MDR_GPIO_Pin_Count > 16  
  #define MDR_Pin_16                 0x00010000UL  /*!< Pin 16 selected */
  #define MDR_Pin_17                 0x00020000UL  /*!< Pin 17 selected */  
  ...
  #define MDR_Pin_31                 0x80000000UL  /*!< Pin 31 selected */

  #define MDR_Pin_All                0xFFFFFFFFUL  /*!< All pins selected */
#else
  #define MDR_Pin_All                0xFFFFUL      /*!< All pins selected */
#endif  

Далее идут определения необходимые для защиты пинов Jtag от перенастройки:

//=================================    Защита Jtag ==============================
// В пины Jtag можно писать только 0, иначе интерфейс отваливается
#if defined (USE_JTAG_A)
  #define PORT_JTAG                 MDR_JTAG_A_PORT
  #define PORT_JTAG_PinSel          MDR_JTAG_A_PINS
  #define PORT_JTAG_FuncSel         MDR_JTAG_A_PINS_FUNC
  #define PORT_JTAG_PDSel           MDR_JTAG_A_PINS_PD

  //  for ESila
  #define PORT_JTAG_Func0Sel        MDR_JTAG_A_PINS_FUNC0
  #define PORT_JTAG_Func1Sel        MDR_JTAG_A_PINS_FUNC1
  #define PORT_JTAG_Func2Sel        MDR_JTAG_A_PINS_FUNC2
  #define PORT_JTAG_Func3Sel        MDR_JTAG_A_PINS_FUNC3 
  #define PORT_JTAG_PWR0            MDR_JTAG_A_PINS_PWR0
  #define PORT_JTAG_PWR1            MDR_JTAG_A_PINS_PWR1  

#if defined (USE_JTAG_B)
  ...

Маски для защиты пинов определены в файле микроконтроллера, например в MDR_1986ВЕ9x.h:

//  PB[0..4] - TDO, TMS, TCK, TDI, TRST
#define   MDR_JTAG_A_PORT           MDR_PORTB
#define   MDR_JTAG_A_PINS           0x0000001FUL
#define   MDR_JTAG_A_PINS_FUNC      0x000003FFUL
#define   MDR_JTAG_A_PINS_PD        0x001F001FUL

//  PB[1,2] - TMS, TCK
#define   MDR_SWD_A_PORT            MDR_PORTB
#define   MDR_SWD_A_PINS            0x00000006UL
#define   MDR_SWD_A_PINS_FUNC       0x0000003CUL
#define   MDR_SWD_A_PINS_PD         0x00060006UL

Выбор отладочного интерфейса, пины которого будут защищены от перенастройки функциями драйвера, выбирается в файле MDR_ConfigXX.h.

Функции переключения выводов GPIO

Функции переключения выводов похожи на реализацию в текущей SPL, с той лишь разницей, что эти функции сделаны инлайновыми, чтобы не терялось быстродействие на вызов отдельной функции. Инлайн реализация будет вставлена прямо в код, вместо вызова функции. Инлайн реализация предпочтительнее чем макрос, потому что при отладке ее можно пройти отладчиком по шагам, макрос же такого не позволяет. Есть несколько наборов функций переключения пинов, в зависимости от того:

  1. Совмещены ли в МК интерфейс JTAG с выводами GPIO или JTAG реализован на выводах не являющимися пинами GPIO.
  2. Кроме этого, пины могут быть совмещены, но защита пинов отладчика выключена в MDR_ConfigXX.h.
  3. Есть ли в МК регистры SET и CLR.
#ifdef PORT_JTAG
  //  Обнуление битов JTAG чтобы не сломать работу отладчика.
  #if defined(MDR_GPIO_HAS_SET_CLEAR)
    __STATIC_INLINE     void MDR_Port_Set       (MDR_PORT_Type *GPIO_Port, uint32_t portData)  {GPIO_Port->RXTX     = portData  & (~JTAG_PINS(GPIO_Port));}
    __STATIC_INLINE     void MDR_Port_SetPins   (MDR_PORT_Type *GPIO_Port, uint32_t pinSelect) {GPIO_Port->RXTX_Set = pinSelect & (~JTAG_PINS(GPIO_Port));}
    __STATIC_INLINE     void MDR_Port_ClearPins (MDR_PORT_Type *GPIO_Port, uint32_t pinSelect) {GPIO_Port->RXTX_Clr = pinSelect;}
    __STATIC_INLINE     void MDR_Port_TogglePins(MDR_PORT_Type *GPIO_Port, uint32_t pinSelect) {GPIO_Port->RXTX = (GPIO_Port->RXTX ^ pinSelect) & (~JTAG_PINS(GPIO_Port));}
  #else
    __STATIC_INLINE     void MDR_Port_Set       (MDR_PORT_Type *GPIO_Port, uint32_t portData)  {GPIO_Port->RXTX = portData & (~JTAG_PINS(GPIO_Port));}
    __STATIC_INLINE     void MDR_Port_SetPins   (MDR_PORT_Type *GPIO_Port, uint32_t pinSelect) {GPIO_Port->RXTX = (GPIO_Port->RXTX |  pinSelect) & (~JTAG_PINS(GPIO_Port));}
    __STATIC_INLINE     void MDR_Port_ClearPins (MDR_PORT_Type *GPIO_Port, uint32_t pinSelect) {GPIO_Port->RXTX = (GPIO_Port->RXTX & ~pinSelect) & (~JTAG_PINS(GPIO_Port));}
    __STATIC_INLINE     void MDR_Port_TogglePins(MDR_PORT_Type *GPIO_Port, uint32_t pinSelect) {GPIO_Port->RXTX = (GPIO_Port->RXTX ^  pinSelect) & (~JTAG_PINS(GPIO_Port));}
  #endif  
#else 
  #if defined(MDR_GPIO_HAS_SET_CLEAR)
    __STATIC_INLINE     void MDR_Port_Set       (MDR_PORT_Type *GPIO_Port, uint32_t portData)  {GPIO_Port->RXTX     = portData;}
    __STATIC_INLINE     void MDR_Port_SetPins   (MDR_PORT_Type *GPIO_Port, uint32_t pinSelect) {GPIO_Port->RXTX_Set = pinSelect;}
    __STATIC_INLINE     void MDR_Port_ClearPins (MDR_PORT_Type *GPIO_Port, uint32_t pinSelect) {GPIO_Port->RXTX_Clr = pinSelect;}
    __STATIC_INLINE     void MDR_Port_TogglePins(MDR_PORT_Type *GPIO_Port, uint32_t pinSelect) {GPIO_Port->RXTX    ^= pinSelect;}    
  #else  
    __STATIC_INLINE     void MDR_Port_Set       (MDR_PORT_Type *GPIO_Port, uint32_t portData)  {GPIO_Port->RXTX  = portData;}
    __STATIC_INLINE     void MDR_Port_SetPins   (MDR_PORT_Type *GPIO_Port, uint32_t pinSelect) {GPIO_Port->RXTX |= pinSelect;}
    __STATIC_INLINE     void MDR_Port_ClearPins (MDR_PORT_Type *GPIO_Port, uint32_t pinSelect) {GPIO_Port->RXTX &= ~pinSelect;}
    __STATIC_INLINE     void MDR_Port_TogglePins(MDR_PORT_Type *GPIO_Port, uint32_t pinSelect) {GPIO_Port->RXTX ^= pinSelect;}
  #endif
#endif

Переключить сигнал данными функциями можно только если пин настроен как выход в функции Port. Если пин регистром FUNC отдан на управление периферийному блоку (функция main, alter, override), то сам блок задает является ли пин входом/выходом и какой уровень на данный пин выводить. Подробнее - Схемотехника портов GPIO и Управление портами через регистры

Настройка выводов - Вариант 1

Для упрощения настройки пинов реализовано несколько вариантов того, как это можно сделать. Первый вариант аналогичен тому, как это сделано в библиотеке SPL. Т.е. все параметры задаются в структуру, которая подается в функцию MDR_Port_Init() с маской того, для каких пинов применить данные настройки. Каждое поле в структуре соответствует конкретному полю в регистрах порта.

typedef struct {
    MDR_PIN_RXTX    RxTx;         //  RXTX
    MDR_OnOff       OutputEnable; //  OE
    MDR_PIN_FUNC    Func;         //  FUNC
    MDR_OnOff       DigMode;      //  ANALOG
    MDR_OnOff       PullUp;       //  PULL_hi
    MDR_OnOff       PullDown;     //  PULL_lo
    MDR_OnOff       OpenDrain;    //  PD_lo
    MDR_PIN_PWR     Power;        //  PWR
  
#ifdef  MDR_GPIO_HAS_GFEN_SCHMT
    MDR_OnOff       InpFilterEn;  //  GFEN
    MDR_OnOff       InpSchmEn;    //  PD_hi
#endif  
} MDR_GPIO_PinCfg;
  
//  Настрока пинов порта в цифровой или аналоговый режим.
//  В MDR_GPIO_PinCfg задается настройка для младшего пина, которая применяется для всех пинов, выбранных в PinSelect
void MDR_Port_Init(MDR_PORT_Type *GPIO_Port, uint32_t pinSelect, MDR_GPIO_PinCfg *pinCfg);

//  Групповая настройка пинов в аналоговый режим
void MDR_Port_InitAnalog(MDR_PORT_Type *GPIO_Port, uint32_t pinSelect);

Данная вариант настройки универсальный, но несколько избыточен, потому что зачастую не требуется указывать все параметры пина. Кода же всегда требуется писать много - завести структуру и проинициализировать все поля, что не выглядит лаконично на экране. По этой причине для настройки пинов в аналоговый режим есть отдельная функция, которой настройки в общем-то и не нужны - MDR_Port_InitAnalog().

Перед вызовом MDR_Port_Init() необходимо подать тактирование на порт GPIO и в случае аналогов 1986ВЕ8Т разблокировать регистры записью в регистр KEY. (Ниже описана функция MDR_GPIO_Enable() которая делает это.)

Настройка выводов - Вариант 2

Чтобы еще больше сократить код реализован второй вариант настройки. Здесь в отдельную группу выделены настройки которые обычно настраиваются одинаково для группы пинов в одном цифровом интерфейсе, например в SPI, внешней шине и т.д., это:

  1. линейный драйвер / открытый сток
  2. мощность вывода (крутизна фронта при переключении)
  3. Фильтр на входе от "иголок" (нет в 1986ВЕ8 и подобных)
  4. Триггер Шмидта (нет в 1986ВЕ8 и подобных).
typedef struct {
  MDR_OnOff       DigMode;      //  ANALOG
  MDR_OnOff       OpenDrain;    //  PD_lo
  MDR_PIN_PWR     Power;        //  PWR
  
#ifdef  MDR_GPIO_HAS_GFEN_SCHMT  
  MDR_OnOff       InpFilterEn;  //  GFEN
  MDR_OnOff       InpSchmEn;    //  PD_hi
#endif
} MDR_PinDig_GroupPinCfg; 


#ifdef MDR_GPIO_HAS_GFEN_SCHMT
  // Остальные МК
  void MDR_Port_InitDigGroupPinCfg(MDR_OnOff pinOpenDrain, MDR_PIN_PWR pinPower, MDR_OnOff InpSchmEn, MDR_OnOff InpFilterEn, MDR_PinDig_GroupPinCfg *groupPinCfg);

  #define MDR_GPIO_INIT_GR(dig, lin, pwr, fltEn, schmEn)  {.DigMode     = dig,   \
                                                           .OpenDrain   = lin    \
                                                           .Power       = pwr    \
                                                           .InpFilterEn = fltEn  \
                                                           .InpSchmEn   = schmEn }  
  
#else
  // 1986ВЕ8, 1923ВК014, Электросила
  void MDR_Port_InitDigGroupPinCfg(MDR_OnOff pinOpenDrain, MDR_PIN_PWR pinPower, MDR_PinDig_GroupPinCfg *groupPinCfg);
  
  #define MDR_GPIO_INIT_GR(dig, lin, pwr)                 {.DigMode     = dig,   \
                                                           .OpenDrain   = lin    \
                                                           .Power       = pwr    }  
#endif

Все это сведено в отдельную структуру MDR_PinDig_GroupPinCfg, которая потом поступает на вход инициализирующей функции с оставшимися параметрами. Заполнить эту структуру можно функцией MDR_Port_InitDigGroupPinCfg(), либо макросом MDR_GPIO_INIT_GR.

//  Инициализация пинов с дополнительными настройками и групповыми.
typedef enum {
  MDR_Pin_Out   = 0,
  MDR_Pin_In    = 1,
  MDR_Pin_In_PU = 2,  //  PullUp
  MDR_Pin_In_PD = 3,  //  PullDown
} MDR_Pin_IO;

//  Функция настраивает пины выбранные маской pinSelect
void MDR_Port_InitDig(MDR_PORT_Type *GPIO_Port, uint32_t pinSelect, MDR_Pin_IO pinInOut, MDR_PIN_FUNC pinFunc, const MDR_PinDig_GroupPinCfg *groupPinCfg);

//  Функция настраивает отдельный пин заданный индексом PinInd
void MDR_Port_InitDigPin(MDR_PORT_Type *GPIO_Port, uint32_t PinInd, MDR_Pin_IO pinInOut, MDR_PIN_FUNC pinFunc, const MDR_PinDig_GroupPinCfg *groupPinCfg);

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

Из понимания того, что настройкой вход или выход управляет сам периферийный блок на который назначен пин, то можно сделать следующее упрощение - при настройке в функцию main, alter, override пины по умолчанию настраиваем как вход. Дальше функция сама переключит пин на выход если для периферии так надо. Это удобно так-же тем, что если в периферии пин используется как вход, но по ошибке функция пина вдруг будет сброшена в port, то пин не станет наружу выводить сигнал вступая в конфликт с внешним источником сигнала (ведь на вход обычно работает внешний выход).

__STATIC_INLINE 
void MDR_Port_InitDigPort(MDR_PORT_Type *GPIO_Port, uint32_t pinSelect, MDR_Pin_IO pinInOut, const MDR_PinDig_GroupPinCfg *groupPinCfg) 
{
  MDR_Port_InitDig(GPIO_Port, pinSelect, pinInOut, MDR_PIN_PORT, groupPinCfg);
}

typedef enum {
  MDR_Pin_PullOff   = MDR_Pin_In,      //  Pull Off
  MDR_Pin_PullUp    = MDR_Pin_In_PU,   //  PullUp
  MDR_Pin_PullDown  = MDR_Pin_In_PD,   //  PullDown
} MDR_Pin_FuncPull;

__STATIC_INLINE 
void MDR_Port_InitDigFunc(MDR_PORT_Type *GPIO_Port, uint32_t pinSelect, MDR_Pin_FuncPull pinPull, MDR_PIN_FUNC pinFunc, const MDR_PinDig_GroupPinCfg *groupPinCfg)
{
  MDR_Port_InitDig(GPIO_Port, pinSelect, (MDR_Pin_IO)pinPull, pinFunc, groupPinCfg);
}

Как видно по листингу,

Данные функции сокращают количество параметров, которые необходимо указать в коде для инициализации пинов. Это делает код лаконичней и легче при чтении.

В качестве примера применения данного варианта настройки можно привести функцию настройки пинов для SPI, файл MDR_SSP.c:

void MDR_SSP_InitPinsGPIO(const MDR_SSP_CfgPinsGPIO *pinsCfg, MDR_PIN_PWR pinsPower)
{
  MDR_PinDig_GroupPinCfg pinPermCfg;
  
#ifdef MDR_GPIO_HAS_GFEN_SCHMT
  MDR_Port_InitDigGroupPinCfg(MDR_Off, pinsPower, MDR_Off, MDR_Off, &pinPermCfg);
#else
  MDR_Port_InitDigGroupPinCfg(MDR_Off, pinsPower, &pinPermCfg);
#endif  
  
  //  CLK
  MDR_GPIO_Enable(pinsCfg->pPinCLK->portGPIO);
  MDR_GPIO_InitDigPin(pinsCfg->pPinCLK->portGPIO, pinsCfg->pPinCLK->pinIndex, MDR_Pin_In, pinsCfg->pPinCLK->pinFunc, &pinPermCfg);
  //  TX
  if (pinsCfg->pPinTX != NULL)
  {
    MDR_GPIO_Enable(pinsCfg->pPinTX->portGPIO);
    MDR_GPIO_InitDigPin(pinsCfg->pPinTX->portGPIO, pinsCfg->pPinTX->pinIndex, MDR_Pin_In, pinsCfg->pPinTX->pinFunc, &pinPermCfg);
  }
  ... аналогично для RX и FSS
}

(Функция MDR_Port_InitDigFunc() была добавлена позднее, чем писалась MDR_SSP_InitPinsGPIO(), поэтому здесь используется функция MDR_GPIO_InitDigPin(). Разница между MDR_Port_InitDigFunc() и MDR_GPIO_InitDigPin() объясняется далее.)

На вход функции задается структура, в которой для каждого сигнала SPI (CLK, RX, TX, FSS) указано:

Дальше функция MDR_SSP_InitPinsGPIO() сама настроит все пины. Также в функцию задается мощность выводов, чем выше скорость обмена по SPI, тем выше необходимо ставить мощность. Но это в свою очередь ведет к увеличению потребления.

Готовые наборы настроек пинов под SSP, которые использовались в примерах, можно найти в файле MDRB_SSP_PinSelect.c, который подтягивает необходимые определения из файла описания конкретной отладочной платы, например MDRB_1986VE92.h. Аналогичные наборы настроек можно найти в тех-же папках для пинов UART и таймера.

Настройка по умолчанию в port

Апофеозом стремления к лаконичности выглядят следующие функции. Они просто настраивают пины на вход или на выход в цифровом режиме, функция port. Т.е. если надо просто попереключать сигнал на пине (помигать светодиодом) - достаточно проинициализировать его функцией MDR_Port_Init_PortOUT(). Если с пина планируется просто считывать внешние "0" или "1" (опрос кнопок), то достаточно инициализации MDR_Port_Init_PortIN().

void MDR_Port_Init_PortOUT   (MDR_PORT_Type *GPIO_Port, uint32_t pinSelect, MDR_PIN_PWR pinPWR);
void MDR_Port_InitPin_PortOUT(MDR_PORT_Type *GPIO_Port, uint32_t pinInd,    MDR_PIN_PWR pinPWR);

void MDR_Port_Init_PortIN   (MDR_PORT_Type *GPIO_Port, uint32_t pinSelect);
void MDR_Port_InitPin_PortIN(MDR_PORT_Type *GPIO_Port, uint32_t pinInd);

Настройка масками регистров - Вариант 3

Функции настройки портов не особо быстродействующие и иногда есть необходимость быстро настроить пины, или перенастраивать их под различное применение. Делать это ранее описанными функциями накладно, разумнее опуститься на уровень регистров.

В общем случае для задания любого регистра необходима маска CLR, которая сотрет поле в регистре и маска SET, которое выставит новое значение поля. В файле MDR_Funcs.h есть специальная функция которая делает это:

static __inline uint32_t MDR_MaskClrSet(uint32_t value, uint32_t maskClr, uint32_t maskSet)
{
  return (value & (~maskClr)) | maskSet;
}

Чтобы использовать подобный подход с масками для настройки пинов, необходимы две структуры в которых содержатся маски для каждого регистра. Тогда вызвав MDR_MaskClrSet() для каждого регистра мы можем быстро перенастроить пины порта или просто настроить их в необходимый режим. Вот как это выглядит в драйвере:

typedef struct {
  MDR_GPIO_ClearCfg MaskCLR;
  MDR_GPIO_SetCfg   MaskSET;
} MDR_Port_ApplyMask;

//  Применение маски в порт, функция читает состояние портов и применяет к ним маски ApplyMask
void MDR_Port_MaskApply(MDR_PORT_Type *GPIO_Port, MDR_Port_ApplyMask *ApplyMask);

//  Применение масок ApplyMask к ранее считанному состоянию регистров порта - readRegs
void MDR_Port_MaskApplyEx(MDR_PORT_Type *GPIO_Port, MDR_Port_ApplyMask *ApplyMask,  MDR_GPIO_SetCfg *readRegs);

//  Чтение регистров порта
void MDR_Port_ReadRegs(MDR_PORT_Type *GPIO_Port, MDR_GPIO_SetCfg *cfgRegs);

#ifdef MDR_GPIO_CFG_SET_CLEAR
  // Применение в порт через регистры SET и CLR.
  void MDR_Port_WriteRegs(MDR_PORT_Type *GPIO_Port, MDR_GPIO_SetCfg *cfgSet, MDR_GPIO_ClearCfg *cfgClr);
#else
  void MDR_Port_WriteRegs(MDR_PORT_Type *GPIO_Port, MDR_GPIO_SetCfg *cfgRegs);
#endif

Функции Read и Write позволяют вычитать и установить значение регистров соответственно. Функции с содержащие в названиях Mask занимаются именно маскированием, т.е. к значению из регистра применяется сначала стирающая маска MaskCLR, потом устанавливающая MaskSET, после маскирования новые значения записываются обратно в регистры функцией MDR_Port_WriteRegs().

Как видно для записи и чтения используется только структура типа MDR_GPIO_SetCfg. Это потому что данная структура содержит значения для каждого регистра. В отличие от этого, структура типа MDR_GPIO_ClearCfg содержит только необходимые стирающие маски. Дело в том, что один пин в регистрах часто занимает одно и тоже положение - смещение и длину. Следовательно можно использовать одну стирающую маску для разных регистров с одинаковым положением полей. В итоге структуры масок такие:

// ==== 1986ВЕ8Т и аналоги ====
  typedef struct {
    uint32_t        clrPins;      // Маска подходит к RXTX, OE, ANALOG, PULL_Up, PULL_Down, PD
    uint32_t        clrFUNC_0_7;
    uint32_t        clrFUNC_8_15;
    uint32_t        clrFUNC_16_23;
    uint32_t        clrFUNC_24_31;
    uint32_t        clrPWR_0_15;
    uint32_t        clrPWR_16_31;
  } MDR_GPIO_ClearCfg;

  // Значения для всех регистров
  typedef struct {               
    uint32_t        RXTX;        
    uint32_t        OE;          
    uint32_t        FUNC_0_7;  
    uint32_t        FUNC_8_15; 
    uint32_t        FUNC_16_23;
    uint32_t        FUNC_24_31;
    uint32_t        ANALOG;    
    uint32_t        PULL_Up;   
    uint32_t        PULL_Down; 
    uint32_t        PD;        
    uint32_t        PWR_0_15;  
    uint32_t        PWR_16_31; 
  } MDR_GPIO_SetCfg;

// ==== Остальные МК ====
  typedef struct {
    uint32_t        clrPins;  // RXTX, OE, ANALOG, FGEN
    uint32_t        clrFUNC;  
    uint32_t        clrHiLo;  // PD, PULL 
    uint32_t        clrPWR;   
  } MDR_GPIO_ClearCfg;
  
  // Значения для всех регистров
  typedef struct {
    uint32_t        RXTX;     
    uint32_t        OE;       
    uint32_t        FUNC;  
    uint32_t        ANALOG;
    uint32_t        PULL;  
    uint32_t        PD;    
    uint32_t        PWR;   
    uint32_t        GFEN;
  } MDR_GPIO_SetCfg;  

Регистровая инициализация

Работая с масками можно настраивать их напрямую ориентируясь на описание регистров. Но это требует внимания и знания полей в регистрах. Зато такая инициализация выполнится быстрее, чем прочие ранее описанные. Для инициализации или переинициализации пинов необходимо:

// Получение маски CLR для набора пинов pinSelect
void MDR_Port_FillClearMask(uint32_t pinSelect, MDR_GPIO_ClearCfg *cfgClr);

Если регистры порта находятся заведомо в обнуленном состоянии, то можно записать регистры напрямую сразу необходимыми значениями:

Вариант 2 в маски

Конфигурационные маски можно получить и с помощью функций. Например можно считать текущее состояние регистров в маски и в масках перенастроить отдельные пины. После применения масок обратно в регистры пины будут работать согласно установленным настройкам. Для таких манипуляций реализованы следующие функции:

// Пины pinSelect в масках настроить в аналоговую функцию
void MDR_Port_MaskAddAnalog(uint32_t pinSelect, MDR_Port_ApplyMask *applyMask);

// Пины pinSelect в масках настроить в цифровую функцию согласно pinInOut, pinFunc, groupPinCfg
void MDR_Port_MaskAdd(uint32_t pinSelect, MDR_Pin_IO pinInOut, MDR_PIN_FUNC pinFunc, const MDR_PinDig_GroupPinCfg *groupPinCfg, MDR_Port_ApplyMask *applyMask);

// Пин pinIndв масках настроить в цифровую функцию согласно pinInOut, pinFunc, groupPinCfg
void MDR_Port_MaskAddPin(uint32_t pinInd, MDR_Pin_IO pinInOut, MDR_PIN_FUNC pinFunc, const MDR_PinDig_GroupPinCfg *groupPinCfg, MDR_Port_ApplyMask *applyMask);

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

//  Переключение необходимых пинов для работы с LCD и возврат в конфигурацию перед захватом.
void MDRB_LCD_CapturePins(void);
void MDRB_LCD_ReleasePins(void);

Инициализация с включением тактирования

Ранее были представлены функции которые работают с портами GPIO, каждый из которых является отдельным периферийным блоком. Но чтобы ядро могло писать в регистры этих периферийных блоков, эти блоки необходимо сначала затактировать. Включение тактовых частот происходит в блоке RST_Clock, и он значительно различен в разных микроконтроллерах. Это приводит к тому, что надо смотреть документацию - в каком регистре PER_Clock блока RST_Clock, каким битом для какого порта включается тактирование. При написании кода для каждого МК эти регистры и код придется менять, что несколько расстраивает. Хотелось бы чтобы была одна функция которая сама включит тактирование для любого порта в любом микроконтроллере. А в 1986ВЕ8Т еще и пропишет регистр KEY, потому что без правильного значения в регистре KEY, остальные регистры порта недоступны даже если включено тактирование. Такие функции есть:

//  Включение доступа к порту
__STATIC_INLINE void MDR_GPIO_Enable(const MDR_GPIO_Port *GPIO_Port)
{
  REG32(GPIO_Port->RST_ClockEn_Addr) |= GPIO_Port->ClockEnaMask;
  
#ifdef MDR_GPIO_HAS_KEY  
  GPIO_Port->PORTx->KEY = MDR_KEY_UNLOCK;
#endif
}

//  Выключение доступа к порту
__STATIC_INLINE void MDR_GPIO_Disable(const MDR_GPIO_Port *GPIO_Port)
{
  REG32(GPIO_Port->RST_ClockEn_Addr) &= ~(1 << GPIO_Port->ClockEnaMask);
  
#ifdef MDR_GPIO_HAS_KEY    
  GPIO_Port->PORTx->KEY = 0;
#endif
}

Но если присмотреться, то здесь использована новая структура MDR_GPIO_Port, а во всех ранее описанных функциях использовалась структура MDR_PORT_Type. Структура MDR_PORT_Type - это описание самих регистров блока порта в памяти микроконтроллера. В отличие от этого, структура же MDR_GPIO_Port является структурой в памяти, в которой собран адрес регистров порта, адрес регистра где надо включить тактирование и маска для включения этого тактирования:

typedef struct {
  // GPIO Port
  MDR_PORT_Type        *PORTx;
  // Clock Enable
  volatile uint32_t*    RST_ClockEn_Addr;
  uint32_t              ClockEnaMask;
} MDR_GPIO_Port;

extern const MDR_GPIO_Port    GPIO_A_Port;
#define MDR_GPIO_A           (&GPIO_A_Port)
... аналогично для портов B,C,D...

Определяются эти структуры в файле MDR_GPIO.c (MDR_GPIO_VE8x.c), необходимые маски для структур определены в файлах описания микроконтроллера:

MDR_GPIO.c:
  //  Привязка портов к включению тактирования
  const MDR_GPIO_Port   GPIO_A_Port = {
    .PORTx            =  MDR_PORTA,
    .RST_ClockEn_Addr =  MDR_CLK_EN_ADDR_PORT_A,
    .ClockEnaMask     = (1 << MDR_CLK_EN_BIT_PORT_A)
  };
  ... аналогично для остальных портов

На примере 1986ВЕ9х, файл MDR_1986VE9x.h:
  #define   MDR_CLK_EN_ADDR_PORT_A    &MDR_CLOCK->PER_CLOCK
  #define   MDR_CLK_EN_ADDR_PORT_B    &MDR_CLOCK->PER_CLOCK
  ...

  #define   MDR_CLK_EN_BIT_PORT_A     MDR_RST_PER__PORTA_CLK_EN_Pos
  #define   MDR_CLK_EN_BIT_PORT_B     MDR_RST_PER__PORTB_CLK_EN_Pos
  ...
  
файл MDR_RST_VE9x_defs.h:
  #define MDR_RST_PER__PORTA_CLK_EN_Pos   (21UL)
  #define MDR_RST_PER__PORTA_CLK_EN_Msk   (0x200000UL)
  #define MDR_RST_PER__PORTB_CLK_EN_Pos   (22UL)
  #define MDR_RST_PER__PORTB_CLK_EN_Msk   (0x400000UL)
  ...

Поскольку новая структура имеет чуть отличное название, PORT заменилось на GPIO, то поменялись и названия в функциях включения тактирования MDR_GPIO_Enable(). Чтобы сохранить синтаксическое однообразие, все ранее описанные функции работающие со структурой порт MDR_PORT_Type, получили аналогичную реализацию для структуры MDR_GPIO_Port. Соответственно в названиях функций PORT поменялось на GPIO.

  // Вариант настройки 1
  #define MDR_GPIO_Init(GP, Sel, Cfg)           MDR_Port_Init((GP)->PORTx, (Sel), (Cfg))
  #define MDR_GPIO_InitAnalog(GP, Sel)          MDR_Port_InitAnalog((GP)->PORTx, (Sel))
  ... и т.д.

Как видно, здесь на самом деле происходит вызов ранее описанных функций, а на вход для них из структуры MDR_GPIO_Port вытаскивается поле MDR_PORT_Type. Это сделано для единообразия и просто потому, что писать постоянно MDR_PORT_TogglePins(MDR_GPIO_A→PORTx, selPins) будет мозолить глаза. Читать MDR_GPIO_TogglePins(MDR_GPIO_A, selPins) на мой взгляд проще.

ИТОГ

Функций для инициализации получилось достаточно много, потому что для разных случаев бывает необходимо что-то свое, либо простота настройки, либо скорость и возможность переключать текущие настройки пинов. При сборке проектов неиспользуемые функции не включаются в результирующую прошивку, поэтому наличие множества функций не сказывается на ее объеме. Судя по всему за это отвечает опция в настройках проекта Keil - "С/С++" - "One ELF Section per Function".

Использовать можно любой вариант настройки, на сколько он подходит под конкретную задачу. Примеры настроек пинов встречаются в примерах для Pack - Examples, потому что без настройки пинов микроконтроллер не смог бы работать со всем что есть вокруг него на плате.