LPCXpresso Урок 5. GPIO input. Подключаем кнопку.

Мигать светодиодом научились, теперь же попробуем реагировать на нажатие кнопки. Продолжаем осваивать LPC13xx в рамках курса для новичков.

Как это не парадоксально, но на вход у LPCXpresso нет никаких интегрированных средств. У той же Discovery от STM и то кнопка имеется. Ну, ничего, зато у нас есть макетное поле.

Собираем схему

Конечно схема это громко сказано, тут и собирать-то нечего. Нам требуется добавить одну кнопку. Можно брать и с фиксацией и обычную тактовую кнопку. Подключаем её следующим образом:

Один вывод кнопки подключаем к контакту GND (земля), коих на плате к частью хватает, второй к P2.10. Почему к нему? Да просто я подумал, что так будет удобнее.

Дорабатываем библиотеку

К сожалению, в библиотеке LPC13xx_Lib отсутствует функция получения состояния вывода порта, аналогичная GPIOSetValue. Что ж, давайте, напишем её сами. Для этого в проекте библиотеки открываем файл gpio.h и после строки с описанием GPIOIntClear добавляем следующий код:
uint32_t GPIOGetValue( uint32_t portNum, uint32_t bitPosi );

Тем самым мы «объявили» что у нас будет функция, принимающая номер порта и номер вывода в порту как значения типа uint32_t и возвращающая его состояние как значение uint32_t.
Раз объявили, надо её и написать. В файле gpio.c добавляем саму функцию
uint32_t GPIOGetValue( uint32_t portNum, uint32_t bitPosi ) {
  uint32_t regVal = 0;
  switch ( portNum )
  {
	case PORT0:
	  regVal = (LPC_GPIO0->DATA >> bitPosi) & 1;
	break;
	case PORT1:
	  regVal = (LPC_GPIO1->DATA >> bitPosi) & 1;
	break;
	case PORT2:
	  regVal = (LPC_GPIO2->DATA >> bitPosi) & 1;
	break;
	case PORT3:
	  regVal = (LPC_GPIO3->DATA >> bitPosi) & 1;
	break;
	default:
	  break;
  }
  return regVal;
}

Вообще то, все то же самое что и в функции вывода. В зависимости от значения параметра port оператор switch выберет для выполнения одну из строк case и перейдет к её выполнению. Все записи case у нас аналогичные и сводятся к чтению значений всех выводов из запрошенного порта. Далее производится сдвиг так, что бы значение интересующего нас вывода оказалось в младшем бите. После чего накладывается маска, оставляя только младший бит значения, и результат сохраняется в переменную regVal.
Если переданное значение не совпало ни с одним значением case, то выполнится строка default, для которой возвращаемое значение остается нулевым.
Таким образом, функция будет возвращать нам 1, в случае если запрошенный вывод порта имеет единичное состояние и 0 во всех остальных случаях (даже когда запрошен не существующий порт).
Всё сохраняем и компилируем библиотеку. Запускать на отладку не надо. Мы ещё не добавили код, который будет нашу функцию использовать.

Пишем программу

Задача будет простая: при нажатии кнопки мигает светодиод.
Возвращаемся в проект в файле main.c добавляем после группы define’ов для светодиода, свою группу define’ов для кнопки:
#define BUTTON_PORT	2		/* Порт для подключения кнопки */
#define BUTTON_BIT	10		/* Номер бита в порту для кнопки */
#define BUTTON_UP	1		/* Кнопка отжата */
#define BUTTON_DOWN	0		/* Кнопка нажата */

Делаем мы это для удобства. Если вы подключите кнопку к другому выводу, то вам будет достаточно изменить соответствующий номер только здесь, и не придется искать по всему коду места обращения к кнопке. Это пока у нас проект небольшой и вы легко можете всё найти, а когда он вырастет, не составит труда пару мест пропустить.
Теперь о том, куда можно кнопку подключить. Да практически к любому выводу. Исключения составляют специализированные выводы, например, USB-DP и USB-DM которые работают только в качестве выводов USB шины. Так же не рекомендую выводы AD4 и P0.10 т.к. их использует отладчик и P0.7 т.к. на нем подключен светодиод. В общем, используйте любой Px.x, за исключением упомянутых.
Теперь доработаем функцию main, приведу её полностью:
int main(void) {
	GPIOInit();
	GPIOSetDir(LED_PORT, LED_BIT, 1);
	GPIOSetDir(BUTTON_PORT, BUTTON_BIT, 0);
	SysTick_Config(SystemCoreClock / 1000);	// настройка таймера на период 1мс
	while(1) {
		if(GPIOGetValue(BUTTON_PORT, BUTTON_BIT) == BUTTON_UP)
			continue;
		GPIOSetValue(LED_PORT, LED_BIT, LED_ON);
		delay_ms(500);
		GPIOSetValue(LED_PORT, LED_BIT, LED_OFF);
		delay_ms(500);
	}
	return 0 ;
}

Собственно и писать нечего. Проинициализировали порты, настроили светодиод на вывод, а кнопку на ввод (третий параметр 0 — по идеи тоже следовало бы через define сделать, ну да ладно). И ушли в цикл обработки.
Обработка простая. Если кнопка в состоянии «не нажата», то возвращаемся к началу цикла (continue). Как только кнопку нажмут, функция GPIOGetValue вернёт нам нуль, который соответствует состоянию BUTTON_DOWN. В результате условие if не выполнится и выполнение цикла продолжится дальше с кода соответствующего миганию. По завершении цикла мигания снова возвращаемся к проверке нажатия кнопки.

Запуск

Собственно как обычно, сохранили, откомпилировали и запустили полученные 3544 байта кода.

Вместо заключения

При добавлении нового кода для работы с периферией контроллера нам больше потребовалось знания в области программирования на C, чем в области самого контроллера. Но это простой пример. Для добавления поддержки скажем I2C, нам потребуется уже более глубокие познания контроллера. Либо же можно будет взять уже готовую библиотеку и снова ограничиться только знанием библиотеки и программирования.
В целом же с изучением Cortex, на мой взгляд, дела обстоять намного проще, чем с 8-ми битными контроллерами. Да, пока информации по ним мало, зато она проще усваивается.
  • 0
  • 12 сентября 2011, 09:38
  • angel5a
  • 1
Файлы в топике: blinky_button.zip

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

RSS свернуть / развернуть
А в родной библиотеке тоже через свитч работает? Алсо можно было и немного покороче (и, возможно, оптимальней) написать:
uint32_t GPIOGetValue( uint32_t portNum, uint32_t bitPosi ) {
  switch ( portNum )
  {
        case PORT0: return (LPC_GPIO0->DATA >> bitPosi) & 1;
        case PORT1: return (LPC_GPIO1->DATA >> bitPosi) & 1;
        case PORT2: return (LPC_GPIO2->DATA >> bitPosi) & 1;
        case PORT3: return (LPC_GPIO3->DATA >> bitPosi) & 1;
        default: return 0;
  }
}
0
  • avatar
  • Vga
  • 12 сентября 2011, 09:53
В родной тоже через switch, по этой причине и тут так же.
Оптимальнее можно и через указатели (что не портируемо) и макросами забубенить (что не позволит использовать переменные).
Очередной раз повторяю: Цель — научить писать под контроллер. Тот кто материал и программирование освоит — сделает оптимально самостоятельно. А кто не освоит, тому и не надо.
Если вам так хочется оптимальности, а вы хотите этого в каждом посте, то почему бы вам самому не написать пару статей про оптимальное программирование, с отчётомм о сэкономленных тактах? (без сарказма)
0
Здесь нету никаких непортируемых фокусов. Всего лишь убраны ненужные действия. Даст ли оно какой-то эффект — зависит в основном от того, хватило ли ума оптимизатору выпилить эти действия из своего варианта.
Алсо у меня нету ни экспрессы, ни кодреда, так что проверять я не буду. Можешь сам сравнить, есть ли выигрыш — но исходный код стал короче и ничуть не менее понятен.
+1
  • avatar
  • Vga
  • 12 сентября 2011, 10:09
Дорабатываем библиотеку
а где найти эту самую библиотеку, чтобы доработать?
0
Читаем курс с начала, первый урок был с флагом «важный». В нем она и была импортирована. Так же в третьем уроке и тоже с флагом важности, она была подключена.
0
у меня на 1114-302 контроллере, не понял, где она там… файлов с окончинием "_LIB" я там не видел…
0
Не обязательно окончание "_LIB". В моём случае по пути "NXP\LPCXpresso_4.0.6_152\lpcxpresso\Examples\NXP\LPC1000\LPC11xx\" должен лежать файл «LPCXpresso1114_cmsis2.zip». Используйте его.
0
Благодарю за помощь. Сам пока не имею возможности установить последнюю версию.
0
Не могли бы вы разместить эту библиотеку на ресурсе? В новых Examples ее нет ни для LPC1343, ни для LPC1769 каторым я пользуюсь. Я сейчас прошел первые 4 урока и очень хотел бы посмотреть на сколько удобней использовать библиотеку.
0
Здравствуйте. Вы сказали, что можно использовать любой пин Px.x, это означает что можно использовать только их (те что начинаются на P), или любые кроме вышеназванных(AD4, P0.7, USB)?
0
Любой, кроме указанных. AD1, AD2,… тоже можно, но в коде у них будет всё равно имя Px.x.
0
Благодарю за ответ.
0
Вы в каждом уроке описывали сколько занимает прошивка. А как узнать какой объем она имеет при загрузке в память микроконтроллера. Я пробовал смотреть свойства .axf файла в папках Debug и Release и в строке размер они имели значение куда большее чем 32кб (может не его надо смотреть). Так как правильно узнать истинное значение?
0
Компилятор выдает статистиуку в конце

Столбик dec это и есть размер в байтах.
0
А data — это разве не ОЗУ?
0
ОЗУ, точнее инициализируемые глобальные переменные(может ещё что). Всё что входит в сегмент data.
0
А инициализируемые не BSS разве? Я имел в виду, разве сегмент data входит в состав самой прошивки, т.е. прошиваемых в флэш-память МК данные? Мне казалось, это неинициализиированные данные в ОЗУ, или я ошибаюсь?
0
Data Segment (Data + BSS + Heap)
The data area contains global and static variables used by the program that are explicitly initialized with a value.
The BSS segment, also known as uninitialized data, starts at the end of the data segment and contains all global variables and static variables that are initialized to zero or do not have explicit initialization in source code.
Дальше википедию цитировать, или сам откроешь?
0
Нет, этой выдержки мне вполне достаточно, чтобы понять, что я спутал Data с BSS.
0
Если хочешь знать вплоть до байта, то в отладчике есть окно дизассемблера — там смотришь до какого адреса разместиласть программа.
Еще вариант подключить какой-нибудь конвертор в хекс (и смотреть по адресам) / бин (смотреть по размеру файла, за частую -8 байт надо ещё).
Вариант иписанный раннее дает значение не совпадающее «в точности до байта» в определеннпых случаях. В частности значения за 34000 вполне возможны (при размере флеша в 32кБ).
0
Частота микроконтроллера LPC1114 50 Мгц. По умолчанию частота работы так и будет 50, или ее надо предварительно настроить?
0
по дефолту используется внутренний RC-осцилятор без PLL, т.е. 12МГц (у 1343, у 1114 не смотрел, но по идеи та же). Другое дело, что при использовании «стандартной» библиотеки (имеется ввиду стартап код cmsis), последняя настраивает на врешний кварц (предполагается 12МГц) с использованием PLL x4, что дает 48МГц. Как настраивать — можно задать либо define'ами в стартап-коде (предпочтительно), либо переписав код самому.
0
1)Так получается что по умолчанию частота 12 МГц, а как узнать на какой частоте работает GPIO порты микроконтроллера? Почему задаю такой вопрос, при простом задании значения пина порта в 0 и затем в 1 этот промежуток занимает 300 наносекунд, что непомерно много для такой операции.
0
GPIO единственное что весит на AHB-Lite шине и которая тактуется системной частотой. По даташиту время нарастания/спада 5нс (кроме i2c у которых 300нс), так что всё норм.
При использовании кода из описанной в комментируемой статье библиотеки, полученный вами результат — более чем потолок (почему комментарий тогда здесь? видимо всё же 48мгц сисклок у вас). При использовании кода из второго урока — похоже потолок при 12МГц сисклока. При использовании маскируемой/побитовой области вывода порта — вроде можно быстрее.
0
knowledgebase.nxp.com/showthread.php?t=1832
Набрел на эту тему на форуме. Неужели операции | и & такие долгие, что у человека получилось в разы увеличить?
И как сильно влияет производительность Debug и Release кода?
0
Да, это следствие выполнения операций read-modify-write на load-store архитектурах. В ассемблере оно выглядит как:
MOV R0, PORTA //предположим, что регистр данных порта называется именно так
OR R0, R0, #PINMASK
MOV PORTA, R0
Причем первая и последняя операции выполняются не такт, а столько, сколько потребуется времени на передачу данных по шине, на которой висит контроллер GPIO. Результат получается очень грустным. Именно поэтому в AVR есть команды, работающие по методу write вместое read-modify-write — это sbi и cbi. В ARM вместо этого есть битбанд, где переключение одного пина тоже является операцией write: MOV PORTA_PIN3_SET, #01.
0
кстати насчет скорости, я сегодня на работе попробовал в цикле просто опускать и поднимать ногу:
while(1)
{
LPC_GPIO1->MASKED_ACCESS[(1 << 9)] = (1 << 9)
LPC_GPIO1->MASKED_ACCESS[(1 << 9)] =~ (1 << 9)
}
Дак опускание и поднятие происходит за 100 нс (значит частота работы GPIO равная 10 МГц), а новая итерация while занимает аж 300 нан, получается период равен 400 нан. Что-то как то уж сильно долго занимают сишные функции (например в данном случае цикл).
У меня стоит задача работы с флешпамятью, у которой конечно временные характеристики достаточно жесткие. И там идет время на меньше 100 нан, а тут простая итерация цикла занимает 300
0
Флеш с жесткими таймингами? Где вы такое откапали?
Для жестких таймингов берите плисины или авр или пик. Кортекс имеет конвеер, и точно 25нс между двумя командами процессора вам не выдаст.
И мой Вам совет: прежде чем считать тики/такты, научитесь понимать их суть и откуда они беруться. Вы же, судя по комментариям, даже не знаете тактовую частоту процессора, а уже жалуетесь на скорость выполнения.
0
Ну, конкретно для этой задачи можно еще взять МК с контроллером внешней памяти. Не знаю правда, какие это МК из LPC.
Кортекс имеет конвеер, и точно 25нс между двумя командами процессора вам не выдаст.
Ну дело таки не столько в конвейере, он довольно предсказуем, сколько в задержках выборки из флэша и кэшировании команд. На ARM с этим можно попытаться бороться, выполняя код из RAM — благо, тут оно подключено вполне полноценной шиной и лютых тормозов как на STM8 быть не должно. А, например, STM32F3 вообще рассчитаны на выполнение кода из RAM, там еще лучше должно быть.

А совет поддерживаю.
0
судя по тому что вы написали комментариями выше (что при подключении cmsis библиотеки, он настраивает частоту на 48), то с частотой определились
0
Спасибо Вам за все комментарии, достаточно подробные. Они мне действительно помогли. Еще вопрос, а есть ли способ не опрашивать постоянно вот так вот в цикле появился ли сигнал что кнопку нажали… чтобы не тратились впустую мощность контроллера и энергия (пусть и небольшая)?
0
Повесить кнопку на прерывание. Но там свои грабли, поэтому туда обычно вешают только кнопку питания. В принципе, проснуться раз в 100мс чтобы обпросить кнопки — не проблема с точки зрения потребляемой энергии.
0
Вы сказали 100мс, это по умолчанию микроконтроллер опрашивает задействованные пины раз в 100мс?
0
Микроконтроллер только флеш опрашивает, на предмет «гони следущую команду». Пины опрашиваешь ты сам, так что и период опроса зависит исключительно от тебя. 100мс — значение для примера.
P.S. Используй кнопку «ответить» под комментарием, на который отвечаешь.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.