Удобная настройка GPIO портов

Настройка GPIOX портов в STM32, как уж повелось, не самое интересное занятие.
Большое разнообразие настроек в определенный момент начинает взрывать мозг, особенно если пользоваться стандартными решениями.
Например, чтобы нам настроить GPIOA.11 на 10МГцовый — выход по типу OPEN_DRAIN c выключенным драйвером — нужно выполнить следующие операции:


GPIOA->BSRR = GPIO_BSRR_BS11;
GPIOA->CRH = (GPIOA->CRH & (~(GPIO_CRH_MODE11|GPIO_CRH_CNF11))) | (GPIO_CRH_MODE11_0 | GPIO_CRH_CNF11_0);


Если взглянуть на код, то он конечно же не отражает того, что мы хотели бы сделать. Намек, что здесь Выход OPEN-DRAIN 10МГц — ну точно нигде не просматривается. Итак, чтобы все это написать, пришлось полазить по даташитам, и углядеть, какие биты чего делают. В общем, процедура не самая легкая, скажу я вам, зато максимальная по быстродействию.

Немного отвлекшись от темы, скажу, что многие неопытные любители быдлокодить найдут у меня много «лишнего», например, операция "GPIOA->CRH & (~(GPIO_CRH_MODE11|GPIO_CRH_CNF11))", которая позволяет полностью очистить «место» для будущих установок "| (GPIO_CRH_MODE11_0 | GPIO_CRH_CNF11_0)". Куда проще пихать инструкции рода "|=","&=~" по месту и без места, не вдаваясь в саму суть этой операции. Что ж, специально для новичков поясню: Если CRH/CRL настраивать не сразу, а постепенно, путем последовательного включения/выключения бит, мы получим на выходе разные режимы работы пина, а это чревато. За это время можно что-нибудь «поджарить», так как обычно в этом случае не отслеживается, в каких именно промежуточных режимах оказывается порт. К сожалению, таких примеров горе-кодеров вполне хватает на просторах нашего РУнета. Что ж, этот пост — одна из попыток переломить ситуацию в лучшую сторону.

Итак, вернемся к теме: Мы установили ODR в единичку, Считали из CRH значение, после очистили «место» для новых настроек, а потом внесли нужные биты и сохранили снова в CRH. Таким образом всего одной операцией «Чтение — модификация — запись» нам удалось полностью сконфигурировать CRH для нашего пина. Повторюсь, что данная операция выносит мозг тому, кто пытается ее осуществить.

Оценка: Читаемость "-", Исполняемый код "+"

Есть другой вариант, который предлагает компания ST Microelectronics — это использование Standart Peripheral Library (SPL). Давайте рассмотрим этот способ:

GPIO_InitTypeDef GPIO_InitStructure; //создаем переменную структуры
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_Init(GPIOA, &GPIO_InitStructure);


Что из этого выливается на выходе — можно просто ужаснуться. Настройка пина порта вырастает в нехилый код, многократно превышающий все разумные размеры. Подробно возмущался по этой теме DI HALT вот здесь.

Оценка: Читаемость "+", Исполняемый код "-"

Мое предложение является уникальным по отношению к стандартным, так как позволяет избавиться от вышеописанных минусов и получить плюсы. Имеется ввиду, используя минимальное количество операций (как в первом способе) добиться максимальной читаемости и упрощения реализации.


#ifndef GPIO_INIT_H
#define GPIO_INIT_H

#define GPIO_MODE_INPUT_ANALOG				0x00
#define GPIO_MODE_INPUT_FLOATING			0x04
#define GPIO_MODE_INPUT_PULL_UP_DOWN	        	0x08
#define GPIO_MODE_OUTPUT2_PUSH_PULL			0x02
#define GPIO_MODE_OUTPUT2_OPEN_DRAIN			0x06
#define GPIO_MODE_OUTPUT2_ALT_PUSH_PULL			0x0A
#define GPIO_MODE_OUTPUT2_ALT_OPEN_DRAIN		0x0E
#define GPIO_MODE_OUTPUT10_PUSH_PULL			0x01
#define GPIO_MODE_OUTPUT10_OPEN_DRAIN			0x05
#define GPIO_MODE_OUTPUT10_ALT_PUSH_PULL		0x09
#define GPIO_MODE_OUTPUT10_ALT_OPEN_DRAIN		0x0D
#define GPIO_MODE_OUTPUT50_PUSH_PULL			0x03
#define GPIO_MODE_OUTPUT50_OPEN_DRAIN		        0x07
#define GPIO_MODE_OUTPUT50_ALT_PUSH_PULL		0x0B
#define GPIO_MODE_OUTPUT50_ALT_OPEN_DRAIN		0x0F
#define GPIO_MODE_RESERVED				0x0C

#define GPIO_MODE_INPUT_PULL_DOWN			0x80
#define GPIO_MODE_INPUT_PULL_UP				0x81
#define GPIO_MODE_OUTPUT2_PUSH_PULL_DOWN		0x20
#define GPIO_MODE_OUTPUT2_PUSH_PULL_UP			0x21
#define GPIO_MODE_OUTPUT2_OPEN_DRAIN_DOWN		0x60
#define GPIO_MODE_OUTPUT2_OPEN_DRAIN_UP			0x61
#define GPIO_MODE_OUTPUT10_PUSH_PULL_DOWN		0x10
#define GPIO_MODE_OUTPUT10_PUSH_PULL_UP			0x11
#define GPIO_MODE_OUTPUT10_OPEN_DRAIN_DOWN	        0x50
#define GPIO_MODE_OUTPUT10_OPEN_DRAIN_UP		0x51
#define GPIO_MODE_OUTPUT50_PUSH_PULL_DOWN		0x30
#define GPIO_MODE_OUTPUT50_PUSH_PULL_UP			0x31
#define GPIO_MODE_OUTPUT50_OPEN_DRAIN_DOWN	        0x70
#define GPIO_MODE_OUTPUT50_OPEN_DRAIN_UP		0x71

#define _INIT_PIN_CRL(PORT,PIN_NUM,PIN_MODE)	PORT->CRL = (PORT->CRL &(~((uint32_t)0x0F<<(((PIN_NUM) & 0x07)<<2)))) | (((uint32_t)(PIN_MODE)&0x0F)<<(((PIN_NUM) & 0x07)<<2))
#define _INIT_PIN_CRH(PORT,PIN_NUM,PIN_MODE)	PORT->CRH = (PORT->CRH &(~((uint32_t)0x0F<<(((PIN_NUM) & 0x07)<<2)))) | (((uint32_t)(PIN_MODE)&0x0F)<<(((PIN_NUM) & 0x07)<<2))
#define _INIT_PIN(PORT,PIN_NUM,PIN_MODE)	((PIN_NUM)<8)? (_INIT_PIN_CRL(PORT,PIN_NUM,PIN_MODE)):(_INIT_PIN_CRH(PORT,PIN_NUM,PIN_MODE))
#define _GPIO_SET_RESET(PORT,PIN_NUM,PIN_MODE) ((PIN_MODE)==0x01)? (PORT->BSRR = (uint32_t)0x01<<(PIN_NUM)) : (PORT->BRR=(uint32_t)0x01<<(PIN_NUM))

#define _GPIO_INIT_PIN(PORT,PIN_NUM,PIN_MODE)		((PIN_MODE)<0x10)? (_INIT_PIN(PORT,PIN_NUM,PIN_MODE))	:\
                (_GPIO_SET_RESET(PORT,PIN_NUM,(PIN_MODE)&0x01), _INIT_PIN(PORT,PIN_NUM,(PIN_MODE)>>4))

#define GPIO_INIT_PIN(PORT,PIN_NUM,PIN_MODE)    _GPIO_INIT_PIN(PORT,PIN_NUM,PIN_MODE)

#define _BITSET(bits) ((uint16_t)((bits % 010) | (bits / 010 % 010) << 1 | (bits / 0100 % 010) << 2 | \
  (bits / 01000 % 010) << 3 | (bits / 010000 % 010) << 4 | (bits / 0100000 % 010) << 5 | (bits / 01000000 % 010) << 6 | \
  (bits / 010000000 % 010) << 7 | (bits / 0100000000 % 010) << 8 | (bits / 01000000000 % 010) << 9 | (bits / 010000000000 % 010) << 10 | \
  (bits / 0100000000000 % 010) << 11 | (bits / 01000000000000 % 010) << 12 | (bits / 010000000000000 % 010) << 13 | \
  (bits / 0100000000000000 % 010) << 14 | (bits / 01000000000000000 % 010) << 15))

#define BIN(bits) _BITSET(0##bits)

#define _BIT_IN_MASK(MASK,BIT)	((MASK) & (1<<(BIT)))? (uint32_t)1<<((BIT)<<2) : 0

#define __MULTI_INIT(PORT,PIN_MASK,PIN_MODE,REG)	(PORT->REG = (PORT->REG &((uint32_t)0x0F*(_BIT_IN_MASK(~(PIN_MASK),7))|\
	(uint32_t)0x0F*(_BIT_IN_MASK(~(PIN_MASK),6))|(uint32_t)0x0F*(_BIT_IN_MASK(~(PIN_MASK),5))|\
	(uint32_t)0x0F*(_BIT_IN_MASK(~(PIN_MASK),4))|(uint32_t)0x0F*(_BIT_IN_MASK(~(PIN_MASK),3))|\
	(uint32_t)0x0F*(_BIT_IN_MASK(~(PIN_MASK),2))|(uint32_t)0x0F*(_BIT_IN_MASK(~(PIN_MASK),1))|\
	(uint32_t)0x0F*(_BIT_IN_MASK(~(PIN_MASK),0))))|((uint32_t)(PIN_MODE)*(_BIT_IN_MASK(PIN_MASK,7))|\
	(uint32_t)(PIN_MODE)*(_BIT_IN_MASK(PIN_MASK,6))|(uint32_t)(PIN_MODE)*(_BIT_IN_MASK(PIN_MASK,5))|\
	(uint32_t)(PIN_MODE)*(_BIT_IN_MASK(PIN_MASK,4))|(uint32_t)(PIN_MODE)*(_BIT_IN_MASK(PIN_MASK,3))|\
	(uint32_t)(PIN_MODE)*(_BIT_IN_MASK(PIN_MASK,2))|(uint32_t)(PIN_MODE)*(_BIT_IN_MASK(PIN_MASK,1))|\
	(uint32_t)(PIN_MODE)*(_BIT_IN_MASK(PIN_MASK,0))))

#define _MULTI_INIT(PORT,PIN_MASK,PIN_MODE) (((PIN_MASK) & 0x00FF)&&((PIN_MASK) & 0xFF00))? (__MULTI_INIT(PORT,(PIN_MASK)>>8,PIN_MODE,CRH),__MULTI_INIT(PORT,(PIN_MASK)&0xFF,PIN_MODE,CRL)):\
								(((PIN_MASK) & 0x00FF)? __MULTI_INIT(PORT,(PIN_MASK)&0xFF,PIN_MODE,CRL) : __MULTI_INIT(PORT,(PIN_MASK)>>8,PIN_MODE,CRH))
#define _MULTI_SET_RESET(PORT,PIN_MASK,PIN_MODE) ((PIN_MODE)==0x01)? (PORT->BSRR = (uint32_t)(PIN_MASK)) : (PORT->BRR=(uint32_t)(PIN_MASK))

#define _GPIO_MULTI_INIT(PORT,PIN_MASK,PIN_MODE)	((PIN_MODE)<0x10)? (_MULTI_INIT(PORT,PIN_MASK,PIN_MODE))	:\
               (_MULTI_SET_RESET(PORT,PIN_MASK,(PIN_MODE)&0x01), _MULTI_INIT(PORT,PIN_MASK,((PIN_MODE)>>4)&0x0F))

#define GPIO_MULTI_INIT(PORT,PIN_MASK,PIN_MODE)  _GPIO_MULTI_INIT(PORT,PIN_MASK,PIN_MODE)


//Example: GPIO_INIT_PIN(GPIOA,3,GPIO_MODE_INPUT_PULL_DOWN);
//Example2: GPIO_MULTI_INIT(GPIOA,BIN(1010101010101010),GPIO_MODE_OUTPUT50_OPEN_DRAIN_DOWN);
#endif


В теле файла есть и примеры.

Итак, чтобы нам настроить порт нужно поместить все данные в макрофункцию:

GPIO_INIT_PIN(GPIO,N,MODE);

где: GPIO — это порт (GPIOA, GPIOB, GPIOC и т.д.)
N — это номер бита. число от 0 до 15
MODE — это режим того самого пина.
MODE может принимать несколько значений, и все они указаны в листинге кода. Описаны как GPIO_MODE_…

Если значения MODE выбираете из первой группы объявлений (GPIO_MODE_ в пределах от 0x00 до 0x0F), то макрос изменяет регистр CRH или CRL только.
Если выбираете из второй, то настраивается так же и регистр ODR (причем первой инструкцией) в зависимости от того, вверх или вниз нам нужно подтянуть.

GPIO_INIT_PIN(GPIOA,3,GPIO_MODE_INPUT_PULL_DOWN);


Эта макрокоманда настроит GPIOA.3 на вход с подтяжкой книзу.

GPIO_INIT_PIN(GPIOA,4,GPIO_MODE_OUTPUT2_OPEN_DRAIN_UP);


Эта макрокоманда настроит GPIOA.4 на выход OPEN-DRAIN, частотой 2 МГц и с выключенным ключом (например, такая подстройка оптимальна для I2C).

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

#include "gpio_init.h"
#define LED_RED_PORT    GPIOA
#define LED_RED_PIN     0
#define LED_GREEN_PORT  GPIOC
#define LED_GREEN_PIN   13

.....
GPIO_INIT_PIN(LED_RED_PORT,LED_RED_PIN,GPIO_MODE_OUTPUT2_PUSH_PULL_DOWN) //первоначально включим красный диод
GPIO_INIT_PIN(LED_GREEN_PORT,LED_GREEN_PIN,GPIO_MODE_OUTPUT2_PUSH_PULL_UP) //первоначально выключим зеленый диод
.....
//Включить красный светодиод:
LED_RED_PORT->BRR = 1<<LED_RED_PIN;
.....
//Выключить красный светодиод:
LED_RED_PORT->BSRR = 1<<LED_RED_PIN;
.....
//Включить зеленый светодиод:
LED_GREEN_PORT->BRR = 1<<LED_GREEN_PIN;
.....
//Выключить зеленый светодиод:
LED_GREEN_PORT->BSRR = 1<<LED_GREEN_PIN;


Итак, мы научились настраивать пин в GPIO одной строкой, но что делать, если нужно настроить несколько выходов порта сразу на один лад? Такая ситуация может быть весьма часто. Например, шина данных для индикатора, или просто массив светодиодов.
И на это есть ответ:

GPIO_MULTI_INIT(PORT,PIN_MASK,PIN_MODE), где

PORT — Порт GPIO (GPIOA, GPIOB, GPIOC и т.д.)
PIN_MASK — Маска устанавливаемых битов, может быть представлена в двоичном виде 0b10101010… установленный бит — пин инициализируем, сброшен — пропускаем. Для поддержки KEIL можно использовать макрос BIN(), переводящий десятичное число, состоящее из нулей и единиц (по виду похожее на двоичное) в реальное двоичное, как если бы число начиналось с 0b
PIN_MODE — режим настройки пинов. Здесь все аналогично как и в GPIO_INIT_PIN
Пример:
GPIO_MULTI_INIT(GPIOA,BIN(1010101010101010),GPIO_MODE_OUTPUT50_OPEN_DRAIN_DOWN);

Установит 2,4,6,8,10,12,14 и 16 пины порта GPIOA с выходом 50 МГц, открытый коллектор, с включенным ключом
Данный макрос занимает всего 14 инструкций ассемблерного кода и аналогичен трем командам: Настроить ODR, CRH и CRL. Если какой-либо регистр не нуждается в настройке, то он пропускается, тем самым сокращая кол-во ассемблерных инструкций до минимального.

Например, шина данных у нас начинается с GPIOA.3 и имеет разрядность 8 бит
тогда опишем все покрасивее:

#include "gpio_init.h"
#define BUS_PORT    GPIOA  //Порт шины
#define BUS_PIN_0   3      //Номер пина порта, соответствующий младшему разряду шины
.....
#define BUS_MASK    ((uint16_t)0xFF<<BUS_PIN_0)
uint8_t data;
.....
//Первоначально настроим на выход 50 МГц типа PUSH-PULL c подтяжкой книзу
GPIO_MULTI_INIT(BUS_PORT,BUS_MASK,GPIO_MODE_OUTPUT50_PUSH_PULL_DOWN); // Здесь ODR регистр используется в настройке
.....
//Настройка шины для чтения данных:
GPIO_MULTI_INIT(BUS_PORT,BUS_MASK,GPIO_MODE_INPUT_FLOATING);
wait();
//читаем данные
data=(BUS_PORT->IDR & BUS_MASK)>>BUS_PIN_0;
.....
//Настройка шины для передачи данных
BUS_PORT->BSRR=(data<<BUS_PIN_0)|(((uint32_t)((~data)<<BUS_PIN_0)&BUS_MASK)<<0x10);//Установим регистр ODR в нужное нам состояние
GPIO_MULTI_INIT(BUS_PORT,BUS_MASK,GPIO_MODE_OUTPUT50_PUSH_PULL); // Здесь ODR регистр не используется в настройке
wait();


Например, если нам захочется еще и разрядность гибко настраивать — код усложнится несущественно:


#include "gpio_init.h"
#define BUS_PORT    GPIOA  //Порт шины
#define BUS_WIDTH   13     //Разрядность шины
#define BUS_PIN_0   3      //Номер пина порта, соответствующий младшему разряду шины
.....
#define BUS_MASK    ((uint16_t)((1<<BUS_WIDTH)-1)<<BUS_PIN_0)
uint16_t data;
.....
//Первоначально настроим на выход 50 МГц типа PUSH-PULL c подтяжкой книзу
GPIO_MULTI_INIT(BUS_PORT,BUS_MASK,GPIO_MODE_OUTPUT50_PUSH_PULL_DOWN); // Здесь ODR регистр используется в настройке
.....
//Настройка шины для чтения данных:
GPIO_MULTI_INIT(BUS_PORT,BUS_MASK,GPIO_MODE_INPUT_FLOATING);
wait();
//читаем данные
data=(BUS_PORT->IDR & BUS_MASK)>>BUS_PIN_0;
.....
//Настройка шины для передачи данных
BUS_PORT->BSRR=(data<<BUS_PIN_0)|(((uint32_t)((~data)<<BUS_PIN_0)&BUS_MASK)<<0x10);//Установим регистр ODR в нужное нам состояние
GPIO_MULTI_INIT(BUS_PORT,BUS_MASK,GPIO_MODE_OUTPUT50_PUSH_PULL); // Здесь ODR регистр не используется в настройке
wait();

Выше описанный код показывает, насколько удобно переназначить шину данных в другое место.

Оценка: Читаемость "+", Исполняемый код "+"

Ну вот, такой вот хедер мы имеем в наличии. Плюсы очевидны.
  • +7
  • 01 августа 2013, 15:50
  • Mihail
  • 1
Файлы в топике: gpio_init.zip

Комментарии (123)

RSS свернуть / развернуть
а если нужно настроить >1 вывода? во что это выльется?
0
  • avatar
  • zubb
  • 01 августа 2013, 16:15
Если настраивать несколько выводов — данная функция в принципе тоже неплохо будет работать. Напишите GPIO_INIT_PIN несколько раз.
Если нужно настроить сразу весь порт (как шина например), то наверное целесообразность применения в данном случае может быть оспорена. Я же не настаиваю, чтобы пихать это во все дыры. Однако если нужно например настроить какую-нибудь ногу отдельно — это самый шикарный способ.
0
Напишите GPIO_INIT_PIN несколько раз
ну и код одинаковый сгенерируется тоже несколько раз. Лучше уж функцию, как в StdperiphLib
-1
ну так код сгенерируется минимальный, как если бы вы писали первым способом. в любом случае функция из SPL перешибет. А об одной инициализации структуры для каждого пина — та еще эпопея.
0
а вообще у меня уже есть задумка, как реализовать настройку нескольких портов сразу на нужный режим
Выглядеть это будет примерно так:

GPIO_MULTI_INIT(GPIO,PIN_MASK,PIN_MODE)

это будет бомба. так что следите за обновлениями статьи!
+2
Лучше для Ардуино такую фиговину напишите. :D
0
Добавил к статье примерчик для использования GPIO_MULTI_INIT при чтении/записи в шину/из шины данных.
0
Напишите GPIO_INIT_PIN несколько раз.

Узнаю Ардуино-стайл.

Намек, что здесь Выход OPEN-DRAIN 10МГц — ну точно не просматривается.

Для этого есть комментарии.

Немного отвлекшись от темы, скажу, что многие «ушлые» любители быдлокодить найдут у меня много «лишнего», например, операция «GPIOA->CRH & (~(GPIO_CRH_MODE11|GPIO_CRH_CNF11))», которая позволяет полностью очистить «место» для будущих установок "| (GPIO_CRH_MODE11_0 | GPIO_CRH_CNF11_0)". Куда проще пихать инструкции рода "|=","&=~" по месту и без места, не вдаваясь в саму суть этой операции.

Чего там позволяет? Операция

x&=y;


расширяется ровно в

x=x & y;

Это принципиально одно и то же. Так что да, у Вас есть лишние буквы. «Очистить место» позволяет простое присвоение:

GPIOA->CRH = (~(GPIO_CRH_MODE11|GPIO_CRH_CNF11)) | (GPIO_CRH_MODE11_0 | GPIO_CRH_CNF11_0);


Тогда все биты, кроме указанных, примут значение «1». Но надо подумать, нужно ли оно.
0
Для этого есть комментарии.
"// See datasheet", угу.
Чего там позволяет? Операция
Оно позволяет дописать к этой строчке команды установки бит — X = X & (~MASK1) | MASK2 не эквивалент X &= (~MASK1) | MASK2.
+3
В этом смысле да, не эквивалент. Привел эквивалент ниже.
0
Для этого есть комментарии
Правила хорошего тона требуют, чтобы программа читалась без всяких комментариев.
x=x & y;
это и ежу понятно. я говорю об использовании этих операций
по типу того, что здесь
Тогда все биты, кроме указанных, примут значение «1». Но надо подумать, нужно ли оно.
вы ошиблись, указав вместо "&" знак "="
GPIOA->CRH & (~(GPIO_CRH_MODE11|GPIO_CRH_CNF11))
0
Ну, насчет правил хорошего тона я спорить не буду — иначе мы с Вами начнем очередной холивар. :) Скажу только, что на этот счет есть разные воззрения.

по типу того, что здесь

А, понял. Тогда согласен.

вы ошиблись, указав вместо "&" знак "="

Да нет, я просто имел в виду немного другое. Опять же, от непонимания исходного посыла.

Я имел в виду, что "=" как раз таки очистит все нетронутые биты, перезаписав регистр чистым результатом битовой операции.
0
Да, и еще я бы таки поставил «лишние» скобки.

GPIOA->CRH = (GPIOA->CRH & (~(GPIO_CRH_MODE11|GPIO_CRH_CNF11))) | (GPIO_CRH_MODE11_0 | GPIO_CRH_CNF11_0);


Запись получается эквивалентной

GPIOA->CRH&=~(GPIO_CRH_MODE11|GPIO_CRH_CNF11);
GPIOA->CRH|=(GPIO_CRH_MODE11_0 | GPIO_CRH_CNF11_0);
0
Запись неэквивалентна, так как в одном случае это одна операция «чтение/модификация/запись», а в другом — их две.
+1
Ну, с точностью это можно сказать, только посмотрев дизассемблированный листинг.
0
однако в моем случае это всегда будет одна операция. Никуда смотреть не надо. А пока порты настраиваются — перестраиваются — можно и попалить что нибудь случаем. А если она одна — пин сразу встает на место, как надо.
+1
Ну, так-то для этого есть bit banding.
0
хорошо что вы упомянули это, однако bit banding-ом вам не удастся настроить пин сразу, так как у каждого пина 4 бита настройки. 4 операции битбандинга не решают проблему настройки пина за один раз без промежуточного переконфигурирования. Про битбандинг я сам писал статью здесь
0
У LPC хорошая на этот случай область маскированного доступа к порту. Можно как к одному пину, так и к нескольким сразу обращаться.
Но то у LPC, причем не у всех (своей неоднотипностью она и уступает STM32).
+1
лишние скопки не нужны, так как операция & и | абсолютно равноправные и выполняются слева направо.
0
выже не пишете (надеюсь) a = b+c-d только так: a = (b+c)-d
0
Во-первых, MISRA C рекомендует явно указывать порядок следования операций.

А, во-вторых, иногда 2+2 не совсем равно 4. И там это просто необходимо.

Так что не стоит полагаться на компилятор…
0
хорошо, если MISRA рекомендует, послушаем ее. Исправлю текст.
+1
Во-первых, MISRA C рекомендует явно указывать порядок следования операций.
Где? Не нашел.
0
GROUP 12: EXPRESSIONS

12.1 Limited dependence should be placed on the C
operator precedence rules in expressions.

12.2 The value of an expression shall be the same under
any order of evaluation that the standard permits.
0
Первое неясно, второе уточняет «должно быть работоспособно при любом порядке, допустимом стандартом». Стандарт явно не позволяет посчитать 2+2*2 как 8.
+1
Почему неясно? Вполне ясно, что не рекомендуется целиком полагаться на порядок вычисления, определяемый приоритетами операторов в выражениях.

Стандарт, может, и не позволяет, но выше я приводил ссылки на случаи, в которых стандарт бессилен по очевидным математическим причинам. И лучше везде следовать хорошей практике, чтобы не пропустить ошибку там, где это и правда важно.
0
Там весьма специфичный случай и там надо явно задать последовательность операций, для которых стандарт не менее явно последовательность не определяет.
+1
Портыц ввода-вывода имею привычку быть описанными как volatile. По факту ни какой оптимизатор не имеет права «оптимизировать» обращение к регистрам переферии. Потому что, казалось бы бесполезная последовательность сброса-установки бита по факту запускает некий внутренний процесс, тем самым влияя на другие компоненты системы.
Записи абсолютно не эквивалентны.
P.S.: что-то перемудрил.
+1
Именно. Особенно это хорошо видно при переключении режимов работы порта с одного ненулевого значения на другое ненулевое. Если делать две записи, как вот тут есть, а не одну, то в промежутке можем получить какую-то фигню вместо корректного состояния линии. 2 -> 0 -> 1 вместо 2 -> 1. Либо нужно делать через отдельную переменную для временного результата, если уж так хочется в несколько строк.
+1
иначе, как я уже сказал, ненароком можно что-нибудь попалить
0
К примеру, у вас есть некоторое изделие, которое использует какие-то там порты. Чтобы легко было ремапить их — лучше описать сверху #define:

#define LED_RED_PORT    GPIOA
#define LED_RED_PIN     0
#define LED_GREEN_PORT  GPIOC
#define LED_GREEN_PIN   13

.....
GPIO_INIT_PIN(LED_RED_PORT,LED_RED_PIN,GPIO_MODE_OUTPUT2_PUSH_PULL_DOWN) //включим красный диод
GPIO_INIT_PIN(LED_GREEN_PORT,LED_GREEN_PIN,GPIO_MODE_OUTPUT2_PUSH_PULL_DOWN) //включим зеленый диод
0
Обновил статейку. теперь добавился метод GPIO_MULTI_INITЖ
GPIO_MULTI_INIT(GPIOA,BIN(1010101010101010),GPIO_MODE_OUTPUT50_OPEN_DRAIN_DOWN);
+1
Именно так, здорово получилось.
0
Для управления и настройки всех портов, у меня выходит так:
PORTA_ON;
	MDR_PORTA->OE		|= BIN(11111111);
	MDR_PORTA->ANALOG	|= BIN(11111111);
	MDR_PORTA->PWR 		|= BIN(01010101);
	PORTA=0;
	
	while (1)
	{		
		PORTA =BIN(00000101);
		delay_CLK(10000000);
		PORTA =BIN(00001010);
		delay_CLK(10000000);
        }


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

Я уже не раз спрашивал, может уже где есть стандартные «людские» или «народные» дефайны, чтоб все пользовались одним и темже…
Ато разнобой получается. Кто-то юзает GPIOA_1 = 0, а я юзаю PORTA =BIN(00000000)…
А кто-то использует стандартно PORT_ResetBits(PORTA, PORT_Pin_1); итд.
Есть люди которые вообще код пишут по Русски =), а у меня не получилось =(…
-1
ну, это уже отрыв от реальности… такие программы можно конечно делать и радоваться, только вот для понимания другими задача сильно усложняется. Интересно глянуть на вашу структуру MDR_PORTA
0
Структуру MDR_PORTA не менял, это из библиотеки миландровского кортекса.
BIN(00000000) этим я заменял хексовые значения, ибо они непонятны и ненаглядны.
Код стал менее понятен, зато быстро писуемо, ака быстрокод =))).
Пишу пока чисто чтоб разобратся как работают там пины итд, потом нужен божеский вид.
Хотя в своих примерах наверно такой метод оставлю, с коментариями.

Код типа:
GPIO_INIT_PIN(GPIOA,4,GPIO_MODE_OUTPUT2_OPEN_DRAIN_UP);
Мне вполне приглянулся. Коротко/быстро/наглядно.

Для описания сразу всех портов нужно примерно такое наверно:
GPIO_ODR(GPIOA,00001111);
GPIO_OPEN(GPIOA,00001111);
GPIO_PWR50(GPIOA,00001111);
GPIO_ANALOG(GPIOA,11110000);

Или

Использовать стандартный метод…
Думаю каждый для себя знает что лучше, никому не угодишь(.
0
GPIO_ODR(GPIOA,00001111);
GPIO_OPEN(GPIOA,00001111);
GPIO_PWR50(GPIOA,00001111);
GPIO_ANALOG(GPIOA,11110000);
Мне понравилось. Надо будет прикинуть мозгами, как это осуществить правильно
0
Весомый минус — если конфигурация изменится и будут задействованы иные пины вместо текущих, то всё подобное придётся переписывать, причём в коде. *Проблема из области поддержки кода*

Если на это забить, то можно.
0
так я же не сказал, «так реализовать», сказал «как осуществить правильно». Кстати, вышестоящий топик подсказал мне как сделать GPIO_MULTI_INIT
0
Не очень удобно оперировать портами, номерами бит и прочим.
Мне показалось удобным использовать вот такой подход:
#define PIN_LED         C, 9, HIGH, MODE_OUTPUT_PUSH_PULL, SPEED_2MHZ

PIN_CONFIGURATION(LED_GREEN);

исходник
0
  • avatar
  • ZiB
  • 01 августа 2013, 17:49
Ну и соотв, если нужно дернуть ножкой
PIN_ON(LED_GREEN); PIN_OFF(LED_GREEN);
0
хм… но в данном случае можно сделать аналогично:

#define PIN_LED        GPIOC,9,GPIO_MODE_OUTPUT2_PUSH_PULL_DOWN


GPIO_INIT(PIN_LED)
0
ну собственно об этом я и говорил…
0
только усложнение макросов ведет к непрозрачности кода, о котором беспокоится teplofizik. Поэтому здесь нужна золотая середина. Универсальность.
0
Не знаю, для меня у меня все прозрачно :)
0
у вас конечно, каша ещё та…
0
ну неудивительно. Каждому понятно лишь то, что делает он сам:) Та каша, которая у меня написана в хедере — можно смело забыть, а в листинге программы все понятно. А инструкции вида INIT(LED_PIN); SET(LED_PIN); RESET(LED_PIN); скрывают много чего интересного)). Хотя, как я уже говорил, привести мои макросы к этому виду не есть проблема — просто дописать несколько дефайнов. Вопрос вкуса, наверное…
0
Ну не знаю, на мой взгляд моя портянка как-то проще воспринимается :)
/*
//------------------------------------------------------------------------------
#define GPIO_PIN_MODE_INPUT(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
		{ GPIO##PORT->MODER &= ~(3UL << (PIN * 2UL)); }
#define GPIO_PIN_MODE_OUTPUT(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
		{ GPIO##PORT->MODER &= ~(3UL << (PIN * 2UL)); \
		  GPIO##PORT->MODER |= (1UL << (PIN * 2UL)); }
#define GPIO_PIN_MODE_ALTERNATE(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
		{ GPIO##PORT->MODER &= ~(3UL << (PIN * 2UL)); \
		  GPIO##PORT->MODER |= (2UL << (PIN * 2UL)); }
#define GPIO_PIN_MODE_ANALOG(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
		{ GPIO##PORT->MODER |= (3UL << (PIN * 2UL)); }

//------------------------------------------------------------------------------
#define GPIO_PIN_SPEED_400KHZ(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
		{ GPIO##PORT->OSPEEDR &= ~(3UL << (PIN * 2UL)); }
#define GPIO_PIN_SPEED_2MHZ(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
		{ GPIO##PORT->OSPEEDR &= ~(3UL << (PIN * 2UL)); \
		  GPIO##PORT->OSPEEDR |= (1UL << (PIN * 2UL)); }
#define GPIO_PIN_SPEED_10MHZ(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
		{ GPIO##PORT->OSPEEDR &= ~(3UL << (PIN * 2UL)); \
		  GPIO##PORT->OSPEEDR |= (2UL << (PIN * 2UL)); }
#define GPIO_PIN_SPEED_40MHZ(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
		{ GPIO##PORT->OSPEEDR |= (3UL << (PIN * 2UL)); }

//------------------------------------------------------------------------------
#define GPIO_PIN_PUSH_PULL(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
		{ GPIO##PORT->OTYPER &= ~(1UL << (PIN)); }
#define GPIO_PIN_OPEN_DRAIN(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
		{ GPIO##PORT->OTYPER |= (1UL << (PIN)); }

//------------------------------------------------------------------------------
#define GPIO_PIN_NO_PULL(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
		{ GPIO##PORT->PUPDR &= ~(3UL << (PIN * 2UL)); }
#define GPIO_PIN_PULL_UP(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
		{ GPIO##PORT->PUPDR &= ~(3UL << (PIN * 2UL)); \
		  GPIO##PORT->PUPDR |= (1UL << (PIN * 2UL)); }
#define GPIO_PIN_PULL_DOWN(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
		{ GPIO##PORT->PUPDR &= ~(3UL << (PIN * 2UL)); \
		  GPIO##PORT->PUPDR |= (2UL << (PIN * 2UL)); }

//------------------------------------------------------------------------------
#define GPIO_PIN0_AF(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
			{ GPIO##PORT->AFR[0] &= ~(15UL << 0); \
			  GPIO##PORT->AFR[0] |= ((ALTERNATE) << 0); }
#define GPIO_PIN1_AF(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
			{ GPIO##PORT->AFR[0] &= ~(15UL << 4); \
			  GPIO##PORT->AFR[0] |= ((ALTERNATE) << 4); }
#define GPIO_PIN2_AF(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
			{ GPIO##PORT->AFR[0] &= ~(15UL << 8); \
			  GPIO##PORT->AFR[0] |= ((ALTERNATE) << 8); }
#define GPIO_PIN3_AF(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
			{ GPIO##PORT->AFR[0] &= ~(15UL << 12); \
			  GPIO##PORT->AFR[0] |= ((ALTERNATE) << 12); }
#define GPIO_PIN4_AF(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
			{ GPIO##PORT->AFR[0] &= ~(15UL << 16); \
			  GPIO##PORT->AFR[0] |= ((ALTERNATE) << 16); }
#define GPIO_PIN5_AF(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
			{ GPIO##PORT->AFR[0] &= ~(15UL << 20); \
			  GPIO##PORT->AFR[0] |= ((ALTERNATE) << 20); }
#define GPIO_PIN6_AF(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
			{ GPIO##PORT->AFR[0] &= ~(15UL << 24); \
			  GPIO##PORT->AFR[0] |= ((ALTERNATE) << 24); }
#define GPIO_PIN7_AF(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
			{ GPIO##PORT->AFR[0] &= ~(15UL << 28); \
			  GPIO##PORT->AFR[0] |= ((ALTERNATE) << 28); }
#define GPIO_PIN8_AF(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
			{ GPIO##PORT->AFR[1] &= ~(15UL << 0); \
			  GPIO##PORT->AFR[1] |= ((ALTERNATE) << 0); }
#define GPIO_PIN9_AF(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
			{ GPIO##PORT->AFR[1] &= ~(15UL << 4); \
			  GPIO##PORT->AFR[1] |= ((ALTERNATE) << 4); }
#define GPIO_PIN10_AF(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
			{ GPIO##PORT->AFR[1] &= ~(15UL << 8); \
			  GPIO##PORT->AFR[1] |= ((ALTERNATE) << 8); }
#define GPIO_PIN11_AF(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
			{ GPIO##PORT->AFR[1] &= ~(15UL << 12); \
			  GPIO##PORT->AFR[1] |= ((ALTERNATE) << 12); }
#define GPIO_PIN12_AF(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
			{ GPIO##PORT->AFR[1] &= ~(15UL << 16); \
			  GPIO##PORT->AFR[1] |= ((ALTERNATE) << 16); }
#define GPIO_PIN13_AF(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
			{ GPIO##PORT->AFR[1] &= ~(15UL << 20); \
			  GPIO##PORT->AFR[1] |= ((ALTERNATE) << 20); }
#define GPIO_PIN14_AF(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
			{ GPIO##PORT->AFR[1] &= ~(15UL << 24); \
			  GPIO##PORT->AFR[1] |= ((ALTERNATE) << 24); }
#define GPIO_PIN15_AF(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
			{ GPIO##PORT->AFR[1] &= ~(15UL << 28); \
			  GPIO##PORT->AFR[1] |= ((ALTERNATE) << 28); }
0
на мой взгляд многа макросов. А у меня многа режимов в макросе. Опять же, дело вкуса.
0
а по сути — у меня их два. И то с появлением последнего(GPIO_MULTI_INIT) GPIO_INIT_PIN можно упразднить. Но оставлю, чтобы компилятору легче работать с ним было.
0
макроса, имею ввиду))
0
Вы заблуждаетесь :)
0
Макросов много. Но это ерунда. А вот если пин переедет (переназначат номер пина, например, где-то в конфиге), то все макросы типа GPIO_PIN15_AF() сделают много ататы. Очень плохо.

Если хочется макрос, то номер регистра и смещение надо вычислять по номер пина PIN, благо это не сложно. И абсолютно избегать таких конструкций!
+1
Прежде чем делать какие то выводы посмотрите исходник целиком…
0
Окей, я его-таки смог найти. Хорошо, что это не используется напрямую. Но макрос можно было и сократить:
#define GPIO_PIN_AF(PORT, PIN, LEVEL, MODE, SPEED, ALTERNATE) \
                          { GPIO##PORT->AFR[PIN / 8] &= ~(15UL << ((PIN & 0x7) * 4); \
                            GPIO##PORT->AFR[PIN / 8] |= ((ALTERNATE) << (PIN & 0x7) * 4); }


Как-то так. Конечно, лучше было бы изменять номер целой перезаписью, а не стиранием-записью, но это мало где может навредить (хотя потенциально может).
0
Можно, но не нужно.
0
не дописал…
0
блять…
последнее время думал над другим подходом, но пока не могу выделить время чтобы привести в порядок все макросы под стм32 и стм8.
0
В моём идеальном мире все сущности должны быть одинаково названы и работать, соотвественно, одинаково=D
+1
мир не идеален
0
Естественно) Это было к тому, что хорошо бы сделать одинаковую систему обозначений, менее привязанную к Эстээму тридцать втором, если уж есть желание производить реформы в макросах=)
0
дык, они у меня ещё с авр-ки ползут… все в одном стиле, так и тянется
0
ну, если вопрос только в обозначении, а не в структуре — то это легко решаемо типа
#define PORTA GPIOA
А вот если там вообще все по разному, то добро пожаловать инструкции вида:
#ifdef STM32F... #else .... #endif
и все дописывать ручками. У меня в принципе в программах так и есть. Если у меня добавляется несовместимый контролер, то это универсальное средство
0
а это для какой серии контроллеров?
0
Конечно можно было бы что-то оптимизировать и упростить, но как говориться работает не трогай :)
0
с этим согласен.
0
Макросы для портов предлагал ZiB. Там все еще более наглядно:
GPIO_RESET(PWR_ON);
GPIO_TOGGLE(ON_LED);

Можно пойти еще дальше и сделать макросы для инициализации сразу всего регистра типа того:
GPIOA->CRL = TO_GPIO_CRL(A, PWR_ON )|TO_GPIO_CRL(A, K3)|TO_GPIO_CRL(A, K2);
Здесь специализируются сразу 3 порта одним присваиванием.
0
  • avatar
  • OlegG
  • 01 августа 2013, 17:55
можете ссылку кинуть, где это? и что такое GPIO_RESET. а то мне как-то не понятно
0
Общий подход по ссылке ZiB несколькими сообщениями выше. Я назвал несколько иначе макросы но общий принцип разработал ZiB. Вот мои макросы

#if !defined(__GPIP_H__)
#define __GPIP_H__
#include "stm32f10x.h"
// определение пина должно выглядить
//#define PIN_NAME PORT, PIN, OUT_MODE_SPEED, SET
// PORT - A, B, C, D и тд - порт ВВ
// PIN - номер бита в порте
// OUT_MODE_SPEED - режим выхода и скорость 
// SET - значение после инициализации для выхода или подтяжка для входа
// скорость. Имеет смысл только для выхода. Объединяется | с режимом
#define OUT10MHZ GPIO_CRL_MODE0_0  // режим выхода среднескоростной - в поле MODE
#define OUT2MHZ  GPIO_CRL_MODE0_1  // низкоскоростной
#define OUT50MHZ (GPIO_CRL_MODE0_1|GPIO_CRL_MODE0_0)  // высокоскоростной

// режимы
#define OUT_PUSHPULL 0 // двутактный выход
#define AOUT_PUSHPULL GPIO_CRL_CNF0_1 //альтернативный двутактный
#define OUT_OPENDRAIN GPIO_CRL_CNF0_0 // открытый сток
#define AUOT_OPENDRAIN (GPIO_CRL_CNF0_0|GPIO_CRL_CNF0_1) //альтернативный открытый сток
//
#define AINPUT 0  //аналоговый вход
#define INPUT GPIO_CRL_CNF0_0  // вход цифровой 
#define INPUT_PULL GPIO_CRL_CNF0_1 //вход цифровой с подтяжкой

//подтяжка
#define PULL_DOWN 0 // подтяжка к земле - необходимо присвоение регистра устновки-сброса
#define PULL_UP   1 // подтяжка к питанию


#define _TO_GPIO_CRL(DEST_PORT, PORT, PIN, MODE, OUT) \
  ((&GPIO##DEST_PORT->CRL == &GPIO##PORT->CRL)*(PIN<8)*(MODE))<<(PIN*4)
#define _TO_GPIO_CRH(DEST_PORT, PORT, PIN, MODE, OUT) \
  ((&GPIO##DEST_PORT->CRH == &GPIO##PORT->CRH)*(PIN>=8)*(MODE))<<((PIN-8)*4)
//макросы используются для присвоения управляющему младшему или старшему регистрам. Макросы должны объединяться через |
#define TO_GPIO_CRL(DEST_PORT, PORT_DESC) _TO_GPIO_CRL(DEST_PORT, PORT_DESC)
#define TO_GPIO_CRH(DEST_PORT, PORT_DESC) _TO_GPIO_CRH(DEST_PORT, PORT_DESC)

#define _TO_GPIO_BSRR(DEST_PORT, PORT, PIN, MODE, OUT) /*((OUT)==0)?0:*/((&GPIO##DEST_PORT->BSRR == &GPIO##PORT->BSRR)*(OUT<<PIN))
//Присваивается регатру установки-сброса. Используется если нужна инициализация или подтяжка
#define TO_GPIO_BSRR(DEST_PORT, PORT_DESC) _TO_GPIO_BSRR(DEST_PORT, PORT_DESC)

#define _GPIO_SET(PORT, PIN, MODE, OUT) GPIO##PORT->BSRR = (1 << PIN)
#define _GPIO_ISSET(PORT, PIN, MODE, OUT) (GPIO##PORT->IDR & (1 << PIN))
#define _GPIO_RESET(PORT, PIN, MODE, OUT) GPIO##PORT->BRR = (1 << PIN)
#define _GPIO_TOGGLE(PORT, PIN, MODE, OUT) GPIO##PORT->ODR ^= (1 << PIN)


//соотв макросы работы с портом
#define GPIO_SET(PIN_DESC) _GPIO_SET(PIN_DESC)
#define GPIO_RESET(PIN_DESC) _GPIO_RESET(PIN_DESC)
#define GPIO_TOGGLE(PIN_DESC) _GPIO_TOGGLE(PIN_DESC)
#define GPIO_ISSET(PIN_DESC) _GPIO_ISSET(PIN_DESC)

#define _GPIO_BB(PORT, PIN, MODE, OUT) (*(uint32_t*)(PERIPH_BB_BASE + ((uint32_t)&(GPIO##PORT->ODR) -  PERIPH_BASE)*32 + PIN*4))
// макрос работы с портом через bit-bang
#define GPIO_BB(PIN_DESC) _GPIO_BB(PIN_DESC)
#endif /* __GPIP_H__ */

Определения пинов:

#define V_BAT     B, 0, AINPUT, 0 //  аналоговый вход
#define BUT1      B, 7, INPUT_PULL, PULL_UP // Вход с подтяжкой к питанию
#define IV1       B, 1, OUT_PUSHPULL|OUT2MHZ, 0 //Выход двухтактный
#define PWM       B, 9, AOUT_PUSHPULL|OUT2MHZ, 0 // Алтернативный выход

Инициализация порта A:
GPIOA->CRL = TO_GPIO_CRL(A, PWR_ON )|TO_GPIO_CRL(A, K3)|TO_GPIO_CRL(A, K2)|TO_GPIO_CRL(A, ADC );
GPIOA->BSRR = TO_GPIO_BSRR(A, K2)|TO_GPIO_BSRR(A, PWR_ON);

Использование:
GPIO_RESET(PWR_ON); // Установка в 0
GPIO_TOGGLE(LCD_LED); // Переключение
GPIO_BB(K1) = 1; // Установка через бит бенд
if ( !GPIO_ISSET(BUT1) ) ... // Проверка состояния
0
инициализация порта выглядит не универсально. нужно CRL, CRH и ODR самому ручками писать. У меня автоматически выбирается ODR, если нужен, и CRL и\или CRH
0
установка ODR через битбенд, не более чем способ «покрасоваться». Есть регистры BSRR/BRR, куда лучше справляющиеся с этой задачей. Особенно регистр BSRR, который может установить и сбросить нужные биты одновременно
0
хотя, если сделать из этого массив, то битбандинг наверное оптимальное решение. Сорри, мысли вслух
0
По моему здесь читал, что битбандинг несколько короче и быстрее. Когда я проверял, использование каждого битбендинга сокращало программу на 2 бата (не помню точно).
По инициализации — молодец! Как понадобится напишу подобное. У инициализации регистра целиком есть некоторые преимущества.
0
это справедливо к операциями с ODR|=. Так как там используется чтение-модификация-запись. Битбандинг по сравнению с BSRR — равноправны, так как имеют одну инструкцию — запись
0
Возможно компилятор оптимизирует лучше конструкции типа
*a=1
чем
*a=0x8000
0
это имеет место быть, но только не у кортексов м
0
Я не настолько хорошо знаю особенности компиляторов, что бы утверждать такое. Я только наблюдал на практике. При применении bit banding бинарник сокращался на 2 байта при замене.
0
Использовал такой же подход, но отказался от него. Сейчас в основном использую простой подход — все пины просто пронумерованы
PA0 0
PA15 15
PB0 0x10 + 0
PB15 0x10 + 15
Достаточно байта.
Режим порта задается тоже байтом
Фукнция иницианизации с параметром uint16 — так что загрузка делается за одну операцию (а не 3-4 как в предлогаемом подходе).
#define PinmMode(pin, mode) PinModeEx((uint16)((mode << 8) | pin)))
если надо сразу для нескольких пинов одного порта
PinsModeA((uint32_t)((mode << 16) | pins_mask))
примерно так
0
  • avatar
  • x893
  • 01 августа 2013, 21:15
только как можно это загрузить за одну операцию? если регистров настроек 2, и если надо и ODR подправить сразу?
Вот поэтому у меня три и выходит. максимум. А может быть и одна, и две…
0
Да не всё ли равно, сколько тактов он потратит на инициализацию: два или десять? =) Конечно, сотня будет уже изрядным перебором, но в пределах пары десятков проблем не вижу.
0
тут идет своего рода конкуренция. если размер кода при всем прочем меньше, значит этот способ лучше. Это как автомобили выбирать. все имеет значение.
0
Скорость хорошо, но реюзабельность, универсальность, понятность (в плане использования), быстрота и удобство поддержки тоже должно учитываться=) Я их вообще на первое место обычно ставлю при нормальных условиях.
0
все зависит от «точки опоры» Если вы решитесь писать на макросах, то и решение любого уровня задач будет понятным, оптимальным и быстродействующим. Как говорится, способ решения будет находиться из действующего положения вещей. Если вы используете SPL, то у вас другим способом решения осуществляются. И вам это понятнее, потому что у вас такое начальное положение вещей. У меня нет проблем с пониманием, как и что осуществить без SPL.
0
Проблем у меня тоже нет и SPL я тоже не пользуюсь почему-то. У SPL какие-то портянки получаются, хотя и настройка точная…
0
аа, ну тогда не знаю, чем эти макросы вам не угодили.
0
Мне они не не угодили =D Когда-то и я пользовался макросами весьма активно.

но я не очень люблю макросы как класс (но это уже не относится к теме вопроса).
0
вполне возможно, что решение задач в какой-то момент будет схожим с тем, как в стандартных библиотеках. но лишь потому, что другого решения просто нет. Но использовать явно избыточный код всегда и везде — это не есть хорошо.
0
Пока код выполняется за гораздо меньшее время, чем требуемое время реакции, то всё ок (покуда это очень удобно для разработки и поддержки). Когда время реакции сильно снижается, тогда уже нужно переходить на более быстрые механизмы. Но пока это не требуется, я не вижу смысла загорождать код макросами (я их не очень люблю за непрозрачность).
+1
эх, видимо кануло в лету то поколение, которое боролось за каждый такт… Представьте, есть задачи масштабного класса с работой на суперкомпьютере. И вот, одна строчка может стоить прикупки дополнительных блоков на миллионы баксов. а если «ручками» писать, так может и комната для него может нужна в 10 раз меньше… у это так, лирика…
0
Когда это реально потребуется, будет и каждый такт, и байт экономиться, это не проблема, можно хоть на ассемблере код написать, если есть уверенность, что он выйдет быстрее.

И не зря же говорили, что преждевременная оптимизация — это корень всех бед.
+2
эх, видимо кануло в лету то поколение, которое боролось за каждый такт…
Канула в лету нужда в борьбе за каждый такт. Сегодня мест, где требуется писать предельно эффективно в ущерб читаемости осталось очень мало.
Все равно, каждый сэкономленный такт потратится в итоге в цикле while(!flag);
+1
так вот я и предлагаю способ решения, позволяющий читаемость оставить на высоком уровне, и в том числе производительность кода тоже.
0
Со стороны читаемости иногда есть дополнительные требования — вроде абстрагирования (для кроссплатформенности) или там возможности передать пин в функцию.
А так, в определенной нише твое решение вполне оправданно.
+1
Видел еще для C++ libstm32pp. Она позволяет писать более читаемый код, но при этом достаточно эффективный благодаря использованию шаблонов. Хотя похоже автор ее малость поздабросил.
0
Я пока не собираюсь отказываться от структурного описания:


#include "gpio.h"
/* Объявлено в gpio.h, как и соответствующие функции
typedef struct
{
    TPort   Port; // enum { PA, PB, PC, ..}
    uint8_t Pin;
} TPin; */

static const TPin Leds[] = { {PA, 0}, {PB, 1}, {PC, 5}, {PD, 14} };
static const int LedCount = sizeof(Leds) / sizeof(Leds[0]);

int main(void)
{
    static bool State = false;
    int i;

    // Инициализация светиков
    for(i = 0; i < LedCount; i++)
    {
        gpio_DigitalOutput(&Leds[i]);
        gpio_OpenDrain(&Leds[i]);
    }
    
    while(1)
    {
        for(i = 0; i < LedCount; i++) gpio_SetValue(&Leds[i], State);
        
        delay(1000000UL);
        State = !State;
    }
}


Типа того. Конечно, кода больше получается (а скорость немного ниже), но зато полная отвязка от платформы и удобство оперирования выводами как единым именованным целым объектом, который можно включать в любую структуру и массив, и сколько угодно раз передавать (по вложенности) во внутренние функции, что не позволяет макросовский подход.
0
Кстати, аналогичные операции можно проводить и над шиной, введя сущность TBus и операции над ней:
typedef struct
{
    TPort Port;
    uint8_t StartPin;
    uint8_t Width;
} TBus;

void gpio_BusOutput(const TBus * Bus);
void gpio_BusInput(const TBus * Bus);
void gpio_BusSet(const TBus * Bus, uint32_t Value);
uint32_t gpio_BusGet(const TBus * Bus);

static const TBus Data = { PA, 0, 8 };

{
    gpio_BusOutput(&Data);
    gpio_BusSet(&Data, COMMAND_WRITE);
    // ...
    gpio_BusInput(&Data);
    Value = (uint8_t)(gpio_BusGet(&Data) & 0xFF);
}


Это так, в качестве идеи. Хотя пару раз пользовался.
0
ну, инициализация порта по внутренним функциям — трудно для понимания, для чего это нужно, однако у всего есть свои плюсы. Макросы конечно подразумевают использование констант, иначе смысл их пропадает. я лишь украсил метод константного присвоения в указанные регистры. И вроде бы, получилось неплохо.
0
Конечно, если рассматривать чисто процесс инициализации, то, может, разницы особо и нет. Скорость я не оцениваю — инициализация обычно проводится один раз и не особо принципиально, сколько времени оно будет шустрить.
Так же нет разницы практически, если используется всего один-два вывода. Написал и забыл.
Но стоит захотеть запихать это в массив, например, чтоб обращаться по номеру вывода (в данной группе), как возникают небольшие сложности. И это не единственное узкое место макросов ><

Возвращаясь к примеру с многоканальным свитчером: простыня LED_INIT(CHANNEL1); LED_INIT(CHANNEL2);, а так же простыня переключателей вместо одного малюсенького циклика или обращения по номеру (&Channel[i])…

Впрочем ладно, я никого не агитирую =D Что кому удобнее…
+1
так можно же предварительно сделать этот массив и указать туда константные величины. И вот, массив готов, обращайся по нему, сколько влезет. GPIOA — это указатели. В массив легко помещаются. ну а номера портов — как бы тоже. Так и вообще в массив можно загнать адреса битов по битбандингу. И вот, просто тупо в эти адреса пишите и читаете значения (если нужно)
и макросы вам здесь очень даже помогут. ТАк что нет нерешаемых задач, если подумать.
0
И получится то же самое, что и у меня)

А битбанд — это завязывает всё на стм32, я этого не хочу. Я не против битбанда, но несколько против жёсткой завязки кода на него.
Этим же я пользуюсь на lpc23xx, stm32, avr, stm8 без каких-либо изменений почти.
+1
ну, здесь конечно все завит от того, какая у вас линейка. я вот пока радуюсь stm-кам. дальше видно будет.
0
работа по битбандингу в этом случае куда круче, чем передавать внутрь функции целые структуры. Размер кода от одной команды типа (*(uint32_t*)(0x4200F523))=1 раздувается в десятки, чтобы установить какой-нить там бит.
0
Стало интересно, а в коментах не нашел.
Попробовал в кокосе+гцц с включенной оптимизацией по размеру -Os:
#include "stm32f10x_gpio.h"
#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "GPIO_INIT.h"
#define st_lib
int main(void)
{
#ifdef st_lib

	GPIO_InitTypeDef  GPIO_InitStructure;                                 //для инициализации порта
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	// leds init on pa0,1
	GPIO_InitStructure.GPIO_Pin                   = GPIO_Pin_11|GPIO_Pin_10;//leds
	GPIO_InitStructure.GPIO_Speed                 =  GPIO_Speed_2MHz;     //макс частота сигнала
	GPIO_InitStructure.GPIO_Mode                  = GPIO_Mode_Out_PP;      //симетричный выход
	GPIO_Init(GPIOB, &GPIO_InitStructure);                                //выполнить инициализацию
#else
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	GPIO_MULTI_INIT(GPIOB,BIN(0000110000000000),GPIO_MODE_OUTPUT2_PUSH_PULL_UP);

#endif
    while(1)
    {
    	volatile int i;
    	for(i=0;i<800000;i++);
    	GPIOB->ODR ^= GPIO_Pin_10;
    }
}

Размер получился следующим:
Вариант ст_либ ч структуры 1880, через GPIO_INIT.h 1696; вообще без инит_гпио, но с включением тактирования портаБ 1680
+1
Вот и получается, 5 раз «поструктурировал» — минус кило флеша…
Мне нравится идея автора, имхо есть смысл и остальное конфигурирование похоже сделать. А хедер назвать по имени автора :)
0
Откуда такие расчёты странные? =) Вы ж не думаете, что stm32f10x_gpio.c будет включаться на каждый вызов?=D
0
Я имел ввиду остальную периферию. Можете добавить(в вариант с ст либ), увеличивается до 2548. Имхо, если делать ч регистры, будет значительно меньше. Не забудьте включить оптимизацию.
П.С. 1 кило имел ввиду 1 кБ полученных по правилам округления. 500 ~ 1499 байт.,
#ifdef MoreProof
	USART_InitTypeDef USART_InitStructure;                                //для инициализации USART

	//настройка модуля USART
		//RCC_APB2PeriphClockCmd (RCC_APB2Periph_USART1,ENABLE);
		USART_InitStructure.USART_BaudRate            = 9600;
		USART_InitStructure.USART_WordLength          = USART_WordLength_8b;
		USART_InitStructure.USART_StopBits            = USART_StopBits_1;
		USART_InitStructure.USART_Parity              = USART_Parity_No ;
		USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
		USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx;
		USART_Init(USART1, &USART_InitStructure);


			//настроить выводы, к которым подключены RX и TX
			//RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);                 //тактирование GPIOA
			GPIO_InitStructure.GPIO_Pin= GPIO_Pin_10;          //линия RX
			GPIO_InitStructure.GPIO_Mode                  = GPIO_Mode_IN_FLOATING;//вход, третье состояние
			GPIO_Init(GPIOA, &GPIO_InitStructure);                                //выполнить инициализацию
			GPIO_InitStructure.GPIO_Pin                   = GPIO_Pin_9;           //линия TX
			GPIO_InitStructure.GPIO_Speed                 = GPIO_Speed_50MHz;     //макс частота сигнала
			GPIO_InitStructure.GPIO_Mode                  = GPIO_Mode_AF_PP;      //альтернативный выход
			GPIO_Init(GPIOA, &GPIO_InitStructure);                                //выполнить инициализацию



		// enable usart1 interrupt
			NVIC_InitTypeDef NVIC_InitStructure;
			NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
			NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
			NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
			NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
			NVIC_Init(&NVIC_InitStructure);
			USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
			NVIC_EnableIRQ(USART1_IRQn);//???
		USART_Cmd(USART1, ENABLE);
#endif
0
Не спорю, что через регистры быстрее — быстрее.

Модуль с функциями уарта большой, он много места занял, да. Я про то, что для второго уарта и порта такого сильного увеличения кода не будет.
0
да просто инициализация структуры сжирает вдвое больше и памяти и времени.
0
спасибо. Не поверил — проверил — убедился! Люблю такой подход к делу!)))
0
А хедер назвать по имени автора :)
ну, вы мне льстите))
0
Уважаемые блогеры, есть вопросик по макросам.

#define TX1 GPIOA, GPIO_Pin_9
GPIO_Init(GPIOA, &GPIO_InitStructure);
Хочу сделать что то типа

#define port(a,b) a
GPIO_Init(port(TX1), &GPIO_InitStructure);

Вот так не хавает, тоесть не делает подстановку в макро port вместо TX1 — GPIOA, GPIO_Pin_9
Вопрос в следующем. как делать то что я хочу, где про это лучше всего почитать?

ПС. Прошу обсуждать не включение порта А (взятый для примера), а суть макросов.
0
Дело в том, что препроцессор сначала подставляет, а потом заменяет.з.
То есть раз в port(a,b) есть два аргумента, он и хочет два: a и b. Подставляется один TX1.
Но после подстановки он подставит и далее, то есть можно использовать двойной уровень макросирования=D

#define TX1 GPIOA, GPIO_Pin_9
#define _getport(pt, pn) pt

#define port(a) _getport(a)
0
Еще ГЦЦ вот так понравилось
#define GPIO_MULTI_INIT(...) GPIO_MULTI_INIT(__VA_ARGS__)

GPIO_MULTI_INIT(TX1,GPIO_MODE_OUTPUT50_ALT_PUSH_PULL);

Без дефайна не компилил. Сразу попробовал с подчеркиванием как у вас, но, после прочтения docstore.mik.ua/manuals/ru/cpp/cpp-5.html особенности рекурсивного макроса, попробовал без _… вроде получилось.
0
К сожалению я был не прав. И вышеуказанный макрос не работал.
Хочу

#define led1 GPIOC, GPIO_Pin_13
GPIO_MULTI_INIT(led1,GPIO_MODE_OUTPUT50_PUSH_PULL);

Заставить работать такое удалось добавлением в gpio_init.h

#define GPIO_MULTI_INIT(...) GPIO_MULTI_INIT_(__VA_ARGS__)
#define GPIO_MULTI_INIT_(PORT,PIN_MASK,PIN_MODE)…

так же, после этого мульти инит работает и в таком виде, в каком работал

GPIO_MULTI_INIT(GPIOC ,GPIO_Pin_13,GPIO_MODE_OUTPUT50_PUSH_PULL);
GPIO_MULTI_INIT(GPIOC ,BIN(0010000000000000),GPIO_MODE_OUTPUT50_PUSH_PULL);

Так же в таком контексте работают стандартные функции- например
GPIO_SetBits(led1);
что удобно и дает единообразный способ работы с пинами.

Главное — не забывать включать тактирование шины и порта. Но это тоже можно автоматизировать, или включать все сразу.
0
а что собой представляет GPIO_Pin_13?

маска?
0
Да, они определены в файле stm32f10x_gpio.h
/** @defgroup GPIO_pins_define
* @{
*/

#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */
#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */
#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */
#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */
#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */
#define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */
0
Ну тогда нужно просто продублировать — углубить функцию типа того:

#define GPIO_MULTI_INItT(PORT, MASK, MODE) _
0
прошу прощения, писал с планшета, что-то его проглючило…
Хотел написать следующее:

_GPIO_MULTI_INIT(PORT,PIN_MASK,PIN_MODE) // здесь то, что написано в GPIO_MULTI_INIT
GPIO_MULTI_INIT(PORT,PIN_MASK,PIN_MODE) _GPIO_MULTI_INIT(PORT,PIN_MASK,PIN_MODE)

Это нужно для раскрытия GPIO_Pin_X
0
собственно, вы это и сделали))
0
обновил статью
0
Еще бы под Stm32f0xx (050) было бы подобное ))
Там несколько другая организация портов, ближе к stm8…
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.