[Программирование микроконтроллеров, Электроника для начинающих] STM32 абстрагируемся от регистров CMSIS при настройке GPIO
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Как известно CMSIS предоставляет доступ к регистрам микроконтроллера. Это конечно хорошо, но не очень удобно. В данной статье речь пойдет о настройке GPIO. Порты ввода-вывода настраиваются довольно просто и если речь идет об одном - двух пинах, можно воспользоваться напрямую регистрами. Но если необходимо сконфигурировать несколько пинов, а тем более динамически менять конфигурацию ( это может потребоваться, например, для проверки подтянутости линий к плюсу при реализации работы с I2C, а потом для переключения на работу с аппаратным I2C ), то гораздо проще обернуть всю работу с регистрами в класс, и пользоваться методами типа setPin/resetPin.Так как работает класс с GPIO я решил его так и назвать. В нем имеется конструктор GPIO( GPIO_TypeDef *port ), который принимает ссылку на порт. Так же в классе есть методы :
- void pinConf ( uint8_t pin_nomber, uint8_t pin_mode ); // режим работы пина
- void setPin( uint8_t pin_nomber ); // установить 1
- void resetPin( uint8_t pin_nomber ); // сбросить пин
- int getPin ( uint8_t pin_nomber ); // считываем состояние пина (reg. IDR)
Модуль класса состоит из двух файлов - gpio.h и gpio.cpp.GPIO.H
#ifndef USER_LIB_GPIO_GPIO_H_
#define USER_LIB_GPIO_GPIO_H_
#include "stm32f103xb.h"
//---------------inputs-------------------------------------------------
#define INPUT_FLOATING 0x4 // вход без подтяжки
#define INPUT_PULL_UP 0x7F // с подтяжкой к питанию
#define INPUT_PULL_DOWN 0xFF // с подтяжкой к "земле"
#define INPUT_ANALOG 0x0 // аналоговый вход
//--------------outputs--------------------------------------------------
#define OUTPUT_OPEN_DRAIN 0x7 // выход открытый сток
#define OUTPUT_PUSH_PULL 0x3 // выход тяни-толкай
//--------------altarnate function---------------------------------------
#define AF_PUSH_PULL 0xB // альтернативная ф-я с выходом тяни-толкай
#define AF_OPEN_DRAIN 0xF // альтернативная функция с открытым стоком
У выводов контроллера есть несколько режимов работы. Они задаются в регистрах GPIOx_CRL ( для выводов от 0 до 7 ) и GPIOx_CRH ( для выводов от 8 до 15 ). На каждый вывод в этих регистрах отводится по 4 бита, которые и задают режим работы. Чтобы не вспоминать каждый раз какие биты нужно прописать на каком месте для определенного режима, удобно переписать все возможные комбинации в макроопределения, что я и сделал. В последствии эти константы будут передаваться в методы для задания режима.
class GPIO {
public:
GPIO( GPIO_TypeDef *port );
void pinConf ( uint8_t pin_nomber, uint8_t pin_mode ); // режим работы пина
void setPin( uint8_t pin_nomber ); // установить 1 на пине
void resetPin( uint8_t pin_nomber ); // сбросить пин
int getPin ( uint8_t pin_nomber ); // считываем состояние пина (reg. IDR)
private:
GPIO_TypeDef *GPIOx;
int pin_m;
};
#endif /* USER_LIB_GPIO_GPIO_H_ */
Далее по коду объявляем класс GPIO. Конструктор класса принимает ссылку на порт (GPIOA, GPIOB и т.д.) и сохраняет ее в приватной переменной GPIOx. Это нужно для того, чтобы при создании объекта и вызове его методов, методы знали с каким портом работать.После конструктора следуют публичные методы класса, образующие интерфейс.
- pinConf ( uint8_t pin_nomber, uint8_t pin_mode ) принимает номер вывода порта и режим работы, который задается макроопределениями в начале файла.
- setPin( uint8_t pin_nomber ) устанавливает вывод в 1. Принимает номер пина порта.
- resetPin( uint8_t pin_nomber ) сбрасывает вывод. Принимает номер вывода.
- getPin ( uint8_t pin_nomber ) возвращает состояние вывода порта (читает регистр IDR)
GPIO.CPP
#define INPUT_PULL_UP_DOWN 0x8 // используется для обоих вариантов
#define GPIO_BITS_MASK 0xF // маска для стирания битов конфигурации
GPIO::GPIO(GPIO_TypeDef *port){
this->GPIOx = port;
// тактируем порт от шины APB1
if ( port == GPIOA){
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
return;
}
if ( port == GPIOB ){
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
return;
}
if ( port == GPIOC ){
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
return;
}
if ( port == GPIOD ){
RCC->APB2ENR |= RCC_APB2ENR_IOPDEN;
return;
}
if ( port == GPIOE ){
RCC->APB2ENR |= RCC_APB2ENR_IOPEEN;
return;
}
return;
}
В конструкторе класса сохраняется ссылка на порт и тактируется выбранный порт.
void GPIO::pinConf ( uint8_t pin_nomber, uint8_t pin_mode ){
this->pin_m = pin_mode; // для методов set/reset Pin если используется альтернативная ф-я
uint8_t offset; // смещение в регистре
uint8_t mode;
// если вход с подтяжкой меняем pin_mode
if ( ( pin_mode == INPUT_PULL_UP ) || ( pin_mode == INPUT_PULL_DOWN ) ){
mode = INPUT_PULL_UP_DOWN;
}//if
if ( pin_nomber < 8 ){
offset = pin_nomber * 4;
this->GPIOx->CRL &= ~( GPIO_BITS_MASK << offset );
this->GPIOx->CRL |= ( mode << offset );
} // if
else if ( pin_nomber > 7 ){
offset = ( pin_nomber - 8 ) * 4;
this->GPIOx->CRH &= ~( GPIO_BITS_MASK << offset );
this->GPIOx->CRH |= ( mode << offset );
} // else
// если режим пулл-ап ставим бит пина в регистре ODR в 1
if ( pin_mode == INPUT_PULL_UP ){
GPIOx->ODR |= ( 1 << pin_nomber );
}
/* доп. условие. если режим задан INPUT_PULL_DOWN то сбрасываем бит пина в 0
* нужно для исключения ситуации, когда сначала назначили режим INPUT_PULL_UP
* а потом где-то в программе переназначили режим INPUT_PULL_DOWN. В этом случае в
* регистре ODR останется 1 и пин все равно будет работать как INPUT_PULL_UP
*/
if ( pin_mode == INPUT_PULL_DOWN ){
GPIOx->ODR &= ~( 1 << pin_nomber );
}
return;
} //pinConf
В методе конфигурации сохраняем заданный режим в переменную pin_m. Это нужно для того, чтобы при использовании методов setPin()/resetPin() и заданном режиме альтернативной функции эти методы не управляли выводом, так как в данном случае управление должно осуществляться из модуля альтернативной функции.Далее проверяем задан ли режим входа с подтяжкой. Если задан то меняем переменную pin_mode так как для обоих режимов входа с подтяжкой биты пина конфигурируются одинаково, а выбор подтяжки к питанию или земле осуществляется записью в регистр ODR 1 или 0.В условии if ( pin_nomber < 8 ) и аналогичном if ( pin_nomber > 7 ) рассчитываем смещение относительно начала регистра CRL или CRH соответственно, учитывая, что на вывод отводится 4 бита, и кладем в переменную offset. Затем сначала затираем биты конфигурации маской GPIO BIT MASK, а затем записываем новые биты конфигурации вывода.if ( pin_mode == INPUT_PULL_UP ) - проверяем, если задан режим подтяжки к питанию, то выставляем в регистре ODR единицу. Аналогично проверяем режим подтяжки к земле и скидываем бит, если условие верно.
void GPIO::setPin( uint8_t pin_nomber ){
// если пин сконфигурирован как альтернативная ф-я ничего не делаем
// т.к. управление пином должно быть из альтернативной ф-и
if ( ( this->pin_m == AF_PUSH_PULL) || ( this->pin_m == AF_OPEN_DRAIN ) ){
return;
}// if
this->GPIOx->BSRR = ( 1 << pin_nomber );
return;
}
В методе setPin() сначала проверяем не задана ли альтернативная функция. Если да, то выходим ничего не делая. Далее в регистре BSSR устанавливаем 1 на соответствующий вывод.
void GPIO::resetPin( uint8_t pin_nomber ){
// если пин сконфигурирован как альтернативная ф-я ничего не делаем
// т.к. управление пином должно быть из альтернативной ф-и
if ( ( this->pin_m == AF_PUSH_PULL) || ( this->pin_m == AF_OPEN_DRAIN ) ){
return;
}// if
this->GPIOx->BRR = ( 1 << pin_nomber );
return;
}
В методе resetPin() все аналогично предыдущему, за исключением того, что пишем в регистр BRR тем самым скидывая вывод.
int GPIO::getPin ( uint8_t pin_nomber ){
uint16_t mask;
mask = ( 1<< pin_nomber);
if ( (this->GPIOx->IDR) & mask) return 1;
else return 0;
}
В методе getPin() создаем маску для сравнения, сравниваем регистр IDR с маской. Если тру - возвращаем 1, иначе 0.Применяется все это дело так :
GPIO *port = new GPIO( GPIOC ); // создаем экземпляр класса, передаем порт GPIOC
port->pinConf( 13, OUTPUT_PUSH_PULL ); //задаем режим выход пуш-пул
port->setPin (13); // установка вывода в 1
port->resetPin (13); // сброс вывода
int value;
value = port->getPin (13); // считываем состояние вывода
Вот собственно и все. Думаю в таком виде работать с портами ввода-вывода гораздо удобнее, чем напрямую с регистрами. Следует отметить, что данный класс осуществляет первичную конфигурацию портов ввода-вывода. Поэтому при работе с аппаратными интерфейсами или АЦП требуется конфигурация еще кучи регистров, обслуживающих эту периферию, но это следует делать в модуле конкретной периферии, наследовав данный класс для первичной конфигурации GPIO. Таким образом отделив яйца от плевел.
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, C, Программирование микроконтроллеров] К вопросу о сложении или как я нашел ошибку в gcc (на самом деле нет)
- [Схемотехника, DIY или Сделай сам, Электроника для начинающих] Максимально универсальный семисегментный дисплей. Часть вторая — Software
- [Управление разработкой, Управление проектами, Фриланс, Производство и разработка электроники, Электроника для начинающих] Как разработать корпус силами фрилансеров — промдизайнеров и конструкторов
- [Реверс-инжиниринг, Программирование микроконтроллеров, Прототипирование, Интернет вещей, DIY или Сделай сам] Подключаемся к станку по изготовлению профлиста и считываем из него прокатную длинну
- [Анализ и проектирование систем, CAD/CAM, Производство и разработка электроники, Электроника для начинающих] Анализ целостности сигналов в PADS Professional (5/6)
- [Системное программирование, FPGA, Программирование микроконтроллеров, Компьютерное железо] Начинаем опыты с интерфейсом USB 3.0 через контроллер семейства FX3 фирмы Cypress
- [Разработка под Arduino, Умный дом, DIY или Сделай сам, Электроника для начинающих, Голосовые интерфейсы] Открываем дверь при помощи голосового ассистента
- [Анализ и проектирование систем, CAD/CAM, Производство и разработка электроники, Электроника для начинающих] Анализ целостности сигналов в PADS Professional (4/6)
- [Программирование микроконтроллеров, Схемотехника] Изучаем RISC-V с нуля, часть 1: Ассемблер и соглашения
- [Совершенный код, Проектирование и рефакторинг, TDD, Программирование микроконтроллеров, Agile] Двигайся быстрее и ломай преграды? Не так быстро, когда дело касается встраиваемых систем (перевод)
Теги для поиска: #_programmirovanie_mikrokontrollerov (Программирование микроконтроллеров), #_elektronika_dlja_nachinajuschih (Электроника для начинающих), #_stm32f103, #_gpio, #_programmirovanie_mikrokontrollerov (
Программирование микроконтроллеров
), #_elektronika_dlja_nachinajuschih (
Электроника для начинающих
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 10:06
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Как известно CMSIS предоставляет доступ к регистрам микроконтроллера. Это конечно хорошо, но не очень удобно. В данной статье речь пойдет о настройке GPIO. Порты ввода-вывода настраиваются довольно просто и если речь идет об одном - двух пинах, можно воспользоваться напрямую регистрами. Но если необходимо сконфигурировать несколько пинов, а тем более динамически менять конфигурацию ( это может потребоваться, например, для проверки подтянутости линий к плюсу при реализации работы с I2C, а потом для переключения на работу с аппаратным I2C ), то гораздо проще обернуть всю работу с регистрами в класс, и пользоваться методами типа setPin/resetPin.Так как работает класс с GPIO я решил его так и назвать. В нем имеется конструктор GPIO( GPIO_TypeDef *port ), который принимает ссылку на порт. Так же в классе есть методы :
#ifndef USER_LIB_GPIO_GPIO_H_
#define USER_LIB_GPIO_GPIO_H_ #include "stm32f103xb.h" //---------------inputs------------------------------------------------- #define INPUT_FLOATING 0x4 // вход без подтяжки #define INPUT_PULL_UP 0x7F // с подтяжкой к питанию #define INPUT_PULL_DOWN 0xFF // с подтяжкой к "земле" #define INPUT_ANALOG 0x0 // аналоговый вход //--------------outputs-------------------------------------------------- #define OUTPUT_OPEN_DRAIN 0x7 // выход открытый сток #define OUTPUT_PUSH_PULL 0x3 // выход тяни-толкай //--------------altarnate function--------------------------------------- #define AF_PUSH_PULL 0xB // альтернативная ф-я с выходом тяни-толкай #define AF_OPEN_DRAIN 0xF // альтернативная функция с открытым стоком class GPIO {
public: GPIO( GPIO_TypeDef *port ); void pinConf ( uint8_t pin_nomber, uint8_t pin_mode ); // режим работы пина void setPin( uint8_t pin_nomber ); // установить 1 на пине void resetPin( uint8_t pin_nomber ); // сбросить пин int getPin ( uint8_t pin_nomber ); // считываем состояние пина (reg. IDR) private: GPIO_TypeDef *GPIOx; int pin_m; }; #endif /* USER_LIB_GPIO_GPIO_H_ */
#define INPUT_PULL_UP_DOWN 0x8 // используется для обоих вариантов
#define GPIO_BITS_MASK 0xF // маска для стирания битов конфигурации GPIO::GPIO(GPIO_TypeDef *port){ this->GPIOx = port; // тактируем порт от шины APB1 if ( port == GPIOA){ RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; return; } if ( port == GPIOB ){ RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; return; } if ( port == GPIOC ){ RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; return; } if ( port == GPIOD ){ RCC->APB2ENR |= RCC_APB2ENR_IOPDEN; return; } if ( port == GPIOE ){ RCC->APB2ENR |= RCC_APB2ENR_IOPEEN; return; } return; } void GPIO::pinConf ( uint8_t pin_nomber, uint8_t pin_mode ){
this->pin_m = pin_mode; // для методов set/reset Pin если используется альтернативная ф-я uint8_t offset; // смещение в регистре uint8_t mode; // если вход с подтяжкой меняем pin_mode if ( ( pin_mode == INPUT_PULL_UP ) || ( pin_mode == INPUT_PULL_DOWN ) ){ mode = INPUT_PULL_UP_DOWN; }//if if ( pin_nomber < 8 ){ offset = pin_nomber * 4; this->GPIOx->CRL &= ~( GPIO_BITS_MASK << offset ); this->GPIOx->CRL |= ( mode << offset ); } // if else if ( pin_nomber > 7 ){ offset = ( pin_nomber - 8 ) * 4; this->GPIOx->CRH &= ~( GPIO_BITS_MASK << offset ); this->GPIOx->CRH |= ( mode << offset ); } // else // если режим пулл-ап ставим бит пина в регистре ODR в 1 if ( pin_mode == INPUT_PULL_UP ){ GPIOx->ODR |= ( 1 << pin_nomber ); } /* доп. условие. если режим задан INPUT_PULL_DOWN то сбрасываем бит пина в 0 * нужно для исключения ситуации, когда сначала назначили режим INPUT_PULL_UP * а потом где-то в программе переназначили режим INPUT_PULL_DOWN. В этом случае в * регистре ODR останется 1 и пин все равно будет работать как INPUT_PULL_UP */ if ( pin_mode == INPUT_PULL_DOWN ){ GPIOx->ODR &= ~( 1 << pin_nomber ); } return; } //pinConf void GPIO::setPin( uint8_t pin_nomber ){
// если пин сконфигурирован как альтернативная ф-я ничего не делаем // т.к. управление пином должно быть из альтернативной ф-и if ( ( this->pin_m == AF_PUSH_PULL) || ( this->pin_m == AF_OPEN_DRAIN ) ){ return; }// if this->GPIOx->BSRR = ( 1 << pin_nomber ); return; } void GPIO::resetPin( uint8_t pin_nomber ){
// если пин сконфигурирован как альтернативная ф-я ничего не делаем // т.к. управление пином должно быть из альтернативной ф-и if ( ( this->pin_m == AF_PUSH_PULL) || ( this->pin_m == AF_OPEN_DRAIN ) ){ return; }// if this->GPIOx->BRR = ( 1 << pin_nomber ); return; } int GPIO::getPin ( uint8_t pin_nomber ){
uint16_t mask; mask = ( 1<< pin_nomber); if ( (this->GPIOx->IDR) & mask) return 1; else return 0; } GPIO *port = new GPIO( GPIOC ); // создаем экземпляр класса, передаем порт GPIOC
port->pinConf( 13, OUTPUT_PUSH_PULL ); //задаем режим выход пуш-пул port->setPin (13); // установка вывода в 1 port->resetPin (13); // сброс вывода int value; value = port->getPin (13); // считываем состояние вывода =========== Источник: habr.com =========== Похожие новости:
Программирование микроконтроллеров ), #_elektronika_dlja_nachinajuschih ( Электроника для начинающих ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 10:06
Часовой пояс: UTC + 5