1. Введение
Я начал изучать STM8 микроконтроллеры. Для начала скачал библиотеку для STM8L152C6 (на али я закал Discovery-STM8L152C6T6 ) и начал смотреть и вникать как она реализована. Библиотека написана в формате CMSIS. Соответственно и доступ к ней аналогичен STM32. Если научился работать с ней, то ARM пойдет как по маслу. Но AVR держит крепко — привык.
Потому решил по аналогии с STM8 написать свой вариант CMSIS для AVR.
2. Обзор CMSIS для STM8
Библиотеку для STM8 открывал AvrStudio4.
Структура библиотека и файлов стандартны для CMSIS. Для того чтобы эффективно пользоваться необходимо понимать что, откуда берется. Итак в файле stm8l15x.h существует следующая структура на примере порта GPIO:
1. В начале определения volatile для отключения оптимизатора.
2. Затем идет описание структуры для GPIO.
Ссылка на структуру указывает на адрес первого регистра ODR
3. Затем идут адреса первых регистров для периферии
4. А дальше идет ассоциация структуры и адреса
3. Обращения к структурам
Рассмотрим виды В зависимости как произведено объявление структуру отличаются обращения к ней.
3.1. Если бы мы объявили структуру как
#define GPIOA *( (GPIO_TypeDef *) GPIOA_BASE )
То обращение происходило бы по значению в формате
GPIO.ODR=0xFF;
3.2. Как вы поняли уже в нашем случае объявление произведено по указателям, поэтому обращение к структуре производится в формате
GPIO->ODR=0xFF;
Второй вариант является более предпочтительным, т.к. при передаче в функцию структуры объявленной по значению пришлось бы копировать в оперативную память в нашем случае 3 байт, а при обращении через указатель лишь адрес первого элемента 2 байта, причем в этом случае не важно сколько элементов будет в структуре.
4. Портирование структуры на AVR
Для того, чтобы работать с периферией через структуру необходимо, чтобы адреса регистров упорядочить по адресу. Порты общего ввода-вывода подходят для нашего учебного примера лучше всего. А далее в структуре ввести их в том же порядке.
Для примера я взял порт D, но с другими портами ситуация аналогична. Начинается с адреса 0х30, а заканчивается 0х31.
#include <avr/io.h>
/**
* IO definitions
* define access restrictions to peripheral registers
*/
#define __I volatile const /*!< defines ‘read only’ permissions */
#define __O volatile /*!< defines ‘write only’ permissions */
#define __IO volatile /*!< defines ‘read / write’ permissions */
// структурный тип данных
typedef struct
{
__I uint8_t pin;
__IO uint8_t ddr;
__IO uint8_t port;
} GPIO_t;
#define GPIOD ((GPIO_t *) &PIND) // создаем указатель на структуру порта D
#define GPIOC ((GPIO_t *) &PINC) // создаем указатель на структуру порта C
int main(void)
{
GPIOD->ddr=0xFF; // определяем порт на выход
while(1)
{
GPIOD->port+=1; // увеличиваем счетчик на 1
}
}
Определения для volatile оставляем без изменений. ЕСЛИ ЗАБУДЕТЕ volatile ДОБАВИТЬ В ВАШУ СТРУКТУРУ, ТО КОМПИЛЯТОР ОПТИМИЗИРУЕТ ЗАПИСЬ БЕЗ ПРЕДУПРЕЖДЕНИЙ (УДАЛИТ) и вы будите голову ломать почему не работает. Создаем новый тип данных для нашей структуры. Порядок перечисления должен совпадать с адресным порядком соответствующих регистров. Далее, создаем указатель на структуру портов, проводим инициализацию, и мигаем портом.
5. Структура и битовые поля
Удобства от структуры которая была сделана выше нет. Мы назвали порты GPIO длиннее. Если необходимо обращаться к отдельным битам, для считывания, или просто пином «помигать», то приходиться городить битовые маски, например:
PORTC|=(1<<7);
PORTC&=~(1<7);
Считаю, данные записи не очень удобными для работы. Для устранения данного недостатка применим битовые маски.
Напишем программу которая мигает на порте С, если высокий уровень на 7 пине, то пин 2 будет мигать, иначе мигаем 4 пином.
#include <avr/io.h>
/**
* IO definitions
*
* define access restrictions to peripheral registers
* определения ограничения доступа к периферийным регистрам
*/
#define __I volatile const /*!< «лишь чтение» */
#define __O volatile /*!< «лишь запись» */
#define __IO volatile /*!< «чтение/запись» */
// typedef struct
{
__I uint8_t pin0:1;
__I uint8_t pin1:1;
__I uint8_t pin2:1;
__I uint8_t pin3:1;
__I uint8_t pin4:1;
__I uint8_t pin5:1;
__I uint8_t pin6:1;
__I uint8_t pin7:1;
//————-
__IO uint8_t ddr0:1;
__IO uint8_t ddr1:1;
__IO uint8_t ddr2:1;
__IO uint8_t ddr3:1;
__IO uint8_t ddr4:1;
__IO uint8_t ddr5:1;
__IO uint8_t ddr6:1;
__IO uint8_t ddr7:1;
//————
__IO uint8_t port0:1;
__IO uint8_t port1:1;
__IO uint8_t port2:1;
__IO uint8_t port3:1;
__IO uint8_t port4:1;
__IO uint8_t port5:1;
__IO uint8_t port6:1;
__IO uint8_t port7:1;
} GPIO_t;
//———————————
#define GPIOD ((GPIO_t *) &PIND)
#define GPIOC ((GPIO_t *) &PINC)
//—————-
int main(void)
{
// инициализация направление ввода
GPIOC->ddr4=1; // пин 4 на вывод
GPIOC->ddr2=1; // пин 2 на вывод
GPIOC->ddr7=0; // пин 7 на ввод
while(1)
{
if (GPIOC->pin7)
{
GPIOC->port2=1;
GPIOC->port2=0;
}
else
{
GPIOC->port4=1;
GPIOC->port4=0;
}
}
}
Данный формат считаю более удобным. Программа написана без битовых масок. Данная программа, можно сказать, написана в формате CMSIS.
Для полного счастья осталось убрать магические числа введя числовые определения. Предположим нужно объявить целый порт на выход. Что же нам теперь по одному биту выставлять? Данный недостаток мы решим добавив ключевое слово UNION. Про него очень хорошо написано в статье.
#include <avr/io.h>
/**
* IO definitions
*
* определения ограничения доступа к периферийным регистрам
*/
#define __I volatile const /*!< ‘лишь чтение’ разрешения */
#define __O volatile /*!< ‘лишь запись’ разрешения */
#define __IO volatile /*!< ‘чтение/запись’ разрешения */
// тип для PORT
typedef enum {
lo=0, // отключаем подтягивающий резистор
hi=1 // включаем подтягивающий резистор } hi_lo;
// //тип для DDR
typedef enum {
in=0, // определяем порт на вход
out=1 // определяем на выход } in_out;
// typedef struct
{
union
{
__I uint8_t pin;
struct
{
__I uint8_t pin0:1;
__I uint8_t pin1:1;
__I uint8_t pin2:1;
__I uint8_t pin3:1;
__I uint8_t pin4:1;
__I uint8_t pin5:1;
__I uint8_t pin6:1;
__I uint8_t pin7:1;
};
};
//————-
union
{
__IO uint8_t ddr;
struct
{
__IO in_out ddr0:1;
__IO in_out ddr1:1;
__IO in_out ddr2:1;
__IO in_out ddr3:1;
__IO in_out ddr4:1;
__IO in_out ddr5:1;
__IO in_out ddr6:1;
__IO in_out ddr7:1;
};
};
//————
union
{
__IO uint8_t port;
struct
{
__IO hi_lo port0:1;
__IO hi_lo port1:1;
__IO hi_lo port2:1;
__IO hi_lo port3:1;
__IO hi_lo port4:1;
__IO hi_lo port5:1;
__IO hi_lo port6:1;
__IO hi_lo port7:1;
};
};
} GPIO_t;
//———————————
#define GPIOD ((GPIO_t *) &PIND)
#define GPIOC ((GPIO_t *) &PINC)
//—————-
int main(void)
{
// инициализация направления ввода
GPIOC->ddr4=out; // пин 4 на вывод
GPIOC->ddr2=out; // пин 2 на вывод
GPIOC->ddr7=in; // пин 7 на ввод
while(1)
{
if (GPIOC->pin7)
{
GPIOC->port2=hi; // включаем подтяг.резистор
GPIOC->port2=lo; // отключаем подтяг.резистор
GPIOC->port=0x0F;
GPIOC->port=0;
}
else
{
GPIOC->port4=hi; // включаем подтяг.резистор
GPIOC->port4=lo; // отключаем подтяг.резистор
GPIOC->port=0xF0;
GPIOC->port=0;
}
}
}
Надеюсь, что статья получилась познавательной и полезной, как для тех кто изучает AVR, так и для перехода на более современные МК STM8, STM32 или ARM другого изготовителя, принципиальной разницы нет. Для более полного понимания процессов запустите программу в симуляторе AvrStudio4 и пощелкайте на F11, прочувствуйте как четко и красиво работает этот подход.
Прикрепленные файлы:
- GPIO.rar (28 Кб)