======Драйвер пинов, GPIO======
Драйвер состоит из общего заголовочника MDR_GPIO.h и двух файлов с имплементацией функций:
- MDR_GPIO_VE8x.c - для 1986ВЕ8Т, 1923ВК014, "Электросила".
- 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
#define MDR_PORT_Type MDR_PORT_Type__Ext
В MDR_1986ВЕ9x.h:
/*=============== GPIO Port ===================*/
#include
#define MDR_PORT_Type MDR_PORT_Type__Base
В MDR_1986ВЕ8.h:
/*=============== GPIO Port ===============*/
#include
В файле 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
Выбор отладочного интерфейса, пины которого будут защищены от перенастройки функциями драйвера, выбирается в файле [[https://startmilandr.ru/doku.php/prog:pack_v6:config#%D0%B7%D0%B0%D1%89%D0%B8%D1%82%D0%B0_%D0%BF%D0%B8%D0%BD%D0%BE%D0%B2_jtag|MDR_ConfigXX.h]].
=====Функции переключения выводов GPIO=====
Функции переключения выводов похожи на реализацию в текущей SPL, с той лишь разницей, что эти функции сделаны инлайновыми, чтобы не терялось быстродействие на вызов отдельной функции. Инлайн реализация будет вставлена прямо в код, вместо вызова функции. Инлайн реализация предпочтительнее чем макрос, потому что при отладке ее можно пройти отладчиком по шагам, макрос же такого не позволяет. Есть несколько наборов функций переключения пинов, в зависимости от того:
- Совмещены ли в МК интерфейс JTAG с выводами GPIO или JTAG реализован на выводах не являющимися пинами GPIO.
- Кроме этого, пины могут быть совмещены, но защита пинов отладчика выключена в MDR_ConfigXX.h.
- Есть ли в МК регистры 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), то сам блок задает является ли пин входом/выходом и какой уровень на данный пин выводить. Подробнее - [[https://startmilandr.ru/doku.php/doc:doclist:gpio_schm|Схемотехника портов GPIO]] и [[https://startmilandr.ru/doku.php/doc:doclist:gpio#%D1%83%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BF%D0%BE%D1%80%D1%82%D0%B0%D0%BC%D0%B8_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_%D1%80%D0%B5%D0%B3%D0%B8%D1%81%D1%82%D1%80%D1%8B|Управление портами через регистры]]
=====Настройка выводов - Вариант 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, внешней шине и т.д., это:
- линейный драйвер / открытый сток
- мощность вывода (крутизна фронта при переключении)
- Фильтр на входе от "иголок" //(нет в 1986ВЕ8 и подобных)//
- Триггер Шмидта //(нет в 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);
}
Как видно по листингу,
* MDR_Port_InitDigPort() - при инициализации в порт не требуется указывать параметр Func, потому что он очевидно будет port.
* MDR_Port_InitDigFunc() - при инициализации пина в функцию к периферийному блоку исключаем вариант того, что пин будет выходом.
Данные функции сокращают количество параметров, которые необходимо указать в коде для инициализации пинов. Это делает код лаконичней и легче при чтении.
В качестве примера применения данного варианта настройки можно привести функцию настройки пинов для 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_GPIO_D
* Индекс пина, например PORT_Pin_3
* функция для пина, например MDR_PIN_ALT
Дальше функция MDR_SSP_InitPinsGPIO() сама настроит все пины. Также в функцию задается мощность выводов, чем выше скорость обмена по SPI, тем выше необходимо ставить мощность. Но это в свою очередь ведет к увеличению потребления.
Готовые наборы настроек пинов под SSP, которые использовались в примерах, можно найти в файле [[https://github.com/StartMilandr/MDR_Pack_v6/blob/master/PACK_Gen/Files/SPL/Boards/MDRB_SSP_PinSelect.c|MDRB_SSP_PinSelect.c]], который подтягивает необходимые определения из файла описания конкретной отладочной платы, например [[https://github.com/StartMilandr/MDR_Pack_v6/blob/master/PACK_Gen/Files/SPL/Boards/Defines/MDRB_1986VE92.h|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, которое выставит новое значение поля. В файле [[https://github.com/StartMilandr/MDR_Pack_v6/blob/master/PACK_Gen/Files/SPL/Drivers/MDR_Funcs.h|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;
====Регистровая инициализация====
Работая с масками можно настраивать их напрямую ориентируясь на описание регистров. Но это требует внимания и знания полей в регистрах. Зато такая инициализация выполнится быстрее, чем прочие ранее описанные. Для инициализации или переинициализации пинов необходимо:
* Заполнить маску типа MDR_GPIO_SetCfg настроив поля регистров под выбранные пины.
* Заполнить маску MDR_GPIO_ClearCfg либо вручную, либо функцией MDR_Port_FillClearMask().
* Применить маски в регистры порта функцией MDR_Port_MaskApply().
// Получение маски CLR для набора пинов pinSelect
void MDR_Port_FillClearMask(uint32_t pinSelect, MDR_GPIO_ClearCfg *cfgClr);
Если регистры порта находятся заведомо в обнуленном состоянии, то можно записать регистры напрямую сразу необходимыми значениями:
* Заполнить маску типа MDR_GPIO_SetCfg настроив поля регистров под выбранные пины.
* Прописать значение регистров функцией MDR_Port_WriteRegs().
====Вариант 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 экрана пересекаются с применением этих пинов по другому назначению. Поэтому для работы с [[https://github.com/StartMilandr/MDR_Pack_v6/blob/master/PACK_Gen/Files/SPL/Boards/MDRB_LCD.h|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 - [[https://github.com/StartMilandr/MDR_Pack_v6/tree/master/PACK_Gen/Files/Examples/All_Boards|Examples]], потому что без настройки пинов микроконтроллер не смог бы работать со всем что есть вокруг него на плате.