Драйвер состоит из общего заголовочника MDR_GPIO.h и двух файлов с имплементацией функций:
Через файл 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.
Функции переключения выводов похожи на реализацию в текущей SPL, с той лишь разницей, что эти функции сделаны инлайновыми, чтобы не терялось быстродействие на вызов отдельной функции. Инлайн реализация будет вставлена прямо в код, вместо вызова функции. Инлайн реализация предпочтительнее чем макрос, потому что при отладке ее можно пройти отладчиком по шагам, макрос же такого не позволяет. Есть несколько наборов функций переключения пинов, в зависимости от того:
#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 и Управление портами через регистры
Для упрощения настройки пинов реализовано несколько вариантов того, как это можно сделать. Первый вариант аналогичен тому, как это сделано в библиотеке 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() которая делает это.)
Чтобы еще больше сократить код реализован второй вариант настройки. Здесь в отдельную группу выделены настройки которые обычно настраиваются одинаково для группы пинов в одном цифровом интерфейсе, например в SPI, внешней шине и т.д., это:
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. Т.е. если надо просто попереключать сигнал на пине (помигать светодиодом) - достаточно проинициализировать его функцией 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);
Функции настройки портов не особо быстродействующие и иногда есть необходимость быстро настроить пины, или перенастраивать их под различное применение. Делать это ранее описанными функциями накладно, разумнее опуститься на уровень регистров.
В общем случае для задания любого регистра необходима маска 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);
Если регистры порта находятся заведомо в обнуленном состоянии, то можно записать регистры напрямую сразу необходимыми значениями:
Конфигурационные маски можно получить и с помощью функций. Например можно считать текущее состояние регистров в маски и в масках перенастроить отдельные пины. После применения масок обратно в регистры пины будут работать согласно установленным настройкам. Для таких манипуляций реализованы следующие функции:
// Пины 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, потому что без настройки пинов микроконтроллер не смог бы работать со всем что есть вокруг него на плате.