8L-Курс, Часть 2 - GPIO

← Часть 1 — Hello светодиод! Содержание Часть 3 — Прерывания →
В прошлой части мы запустили мигалку светодиодом. Теперь пришла пора разобраться с тем, как она работает и как устроен модуль GPIO в STM8.


Железо
Порты у STM8 обозначаются так-же как и у AVR — буквой. В нашей мигалке использовался порт D. Каждый порт состоит из 8 пинов, что тоже напоминает AVR, да и вообще любые восьмибитные МК. Впрочем, на корпусах с небольшим количеством выводов, некоторые порты обрезаны и половины пинов нету. В МК на модуле для PB2 именно такая фигня:

Зато вся периферия (входы АЦП, входы/выходы таймеров) осталась на тех-же ножках, что и в более крупных корпусах. А значит, можно без проблем переносить программы с одного МК на другой (к примеру прошивка для дискавери будет нормально работать и на модуле).

Несколько выводов заняты под питание. На картинке их всего два (Vss1 и Vdd1), но в многоногих корпусах бывает несколько пар. Тогда подключать надо их все, не забывая ни одной пары. Иначе потом можно получить странные глюки в работе МК.

Один вывод занят под сигнал сброса — NRST. Причем его можно программно перевести в обычный режим и он станет (почти) нормальным пином GPIO под названием A1. «Почти» — т.к. на него нельзя повесить прерывание, и нельзя отключить подтягивающий резистор.

Еще один вывод занят под питание для встроенного LCD-контроллера. Если LCD не используется, то этот пин можно не трогать.

В итоге, из 32 выводов у нас остается 28 (или 29 если выключить сброс) под GPIO. Вот о них и будет рассказ.

Электрика

На картинке с распиновкой, что висит чуть выше, указаны только названия выводов, без лишних подробностей. Все подробности собраны в таблице ниже («Medium density STM8L15x pin description», в даташите на МК). Для каждого вывода там указано несколько параметров.


I/O level — обозначает максимальное напряжение, которое можно подавать на пин. TT — значит, что пин спокойно выдержит напряжение в 3.6V (не зависимо от напряжения питания МК). А FT означает, что и 5V ему не страшны. Правда FT-пинов всего два — это C0 и C1, которые используются для работы с шиной I2C. Если в этой колонке ничего не написано, значит на вывод нельзя подавать напряжение выше чем напряжение питания МК.
Floating Input — показывает, может ли пин работать как вход без подтягивающего резистора (A1 — на котором NRST, не может — там подтяжка включена всегда), а wpu (weak pull-up) — есть ли на этом пине подтягивающий резистор (на пинах C1 и C0 его нет). Жирным тут выделен тот режим, в котором пин будет находится при старте МК — почти для всех это вход без подтяжки. Номинал подтянивающего резистора около 40кОм, для всех пинов (в том числе и NRST).

Не все пины могут отдавать одинаковый ток, когда настроены на выход. Те, что могут отдать до 20мА, обозначены SH (в графе High sink/source), а «слабые» остались без этой отметки. И это опять C0 и C1 — которые могут пропустить через себя ток всего в 5мА. А еще есть пин A0, который кроме отладки через SWIM, предназначен для подключения ИК светодиода или похожей мощной нагрузки и может «втянуть» ток до 80mA (т.е. такой он может пропустить через себя, когда выдает низкий уровень).

Колонки OD (open drain) и PP (push-pull) показывают может ли вывод работать в режиме открытого коллектора (или «открытого стока»), когда вместо высокого уровня он переключается на вход. PP, соответсвенно обозначает возможность работы в режиме Push-pull, когда пин может выдавать и низкий, и высокий уровни. Почти все пины могут работать в режиме Push-pull, и если нужно переключаться в режим open-drain. Исключение составляют, опять-же, C0 и C1, которые кроме open-drain, больше никак работать не могут. Имейте это ввиду, при распределении выводов.

Еще раз, кратко, об электрике:
Максимальный ток, выдаваемый ножкой — 20мА (кроме C1 и C0 — у которых 5мА, и A0 — способного «втянуть» до 80мА)
Общий максимальный ток камня — 80мА (т.е. ток через все пины не должен превышать эту отметку).
Максимальное напряжение питания — 3.6V для STM8L и 5.5V для STM8S.
Минимальное — 1.8V для STM8L и 2.7V для S. Причем в STM8L можно отключить flash память и выполнять код из оперативки — тогда они могут работать и при напряжении 1.55V.

Программирование
C точки зрения программиста, GPIO порт в STM8 это пять регистров:
Px_ODR — значение, которое выдается в порт
Px_IDR — текущее состояние порта
Px_DDR — направление (вход или выход)
Px_CR1 — настройки раз
Px_CR2 — настройки два
(вместо x — буква порта)

Первые три регистра наверное ничего нового ни для кого не представляют. На всякий случай устрою ликбез:

Каждый бит Px_DDR отвечает за направление соответствующей ножки МК. 1 — ножка настроена на выход, 0 — на вход.

Биты в Px_ODR устанавливают уровень на ножке (если она настроена на выход). 0 — низкий уровень, 1 — высокий. Причем в зависимости от значения в CR1 (см. ниже), высокий уровень может быть упразднен — пин работает в режиме открытого стока. Уровни STM8 держит очень неплохо — без нагрузки ножка прижатая к земле выдает 2.3мВ (и скорее всего, это погрешность моего мультиметра). Под нагрузкой в 1мА напряжение уже составляет 26мВ, а при нагрузке 20мА (предельное значение) — 556мВ. Если же наш пин настроен на вход, то запись в ODR ничего не даст. Чтение из Px_ODR тоже возможно и тогда мы прочитаем последнее записанное туда значение (даже если на ножке сейчас другой уровень).

А вот из Px_IDR можно прочитать текущий уровень на ножках. И на этом можно лаконично закончить описание этого регистра, ибо больше тут ловить нечего :)

Регистры CR1 и CR2 работают по-разному в зависимости от значения соответсвующего бита в регистре DDR (то есть от того, на вход настроен пин или на выход).

Если пин настроен на вход, то бит в CR1 управляет внутренним подтягивающим резистором. Единица включает его, а ноль отключает — и пин просто болтается в воздухе. Вход без подтяжки (и ни к чему не подключенный) ловит помехи, которые переключают триггеры во входном каскаде порта. А триггеры, переключаясь, кушают неслабый ток. Сотню микроампер висящие без подтяжки выводы могут накинуть запросто. Словом, если устройство должно потреблять как можно меньше тока, надо прибить все пины к какому-либо постоянному уровню. Не важно, при помощи подтяжки или как либо иначе — главное чтобы не переключались от помех.

Подтягивающие резисторы тут довольно хилые — около 38-40 кОм (это при комнатной температуре). В реальных устройствах (а не макетах на столе) лучше ставить внешние подтягивающие резисторы на кнопки. Номиналом 10к или меньше. Особенно это важно если до кнопок идут длинные провода, и/или рядом есть источники помех. Иначе можно получить ложные срабатывания.

Регистр CR2 в этом случае [когда пин настроен на вход] отвечает за прерывание от пина — если там единица, то прерывание разрешено. В STM8 можно ловить прерывания с любой ножки (кроме A1 — NRST). Хотя и тут есть свои хитрости, которые ограничивают свободу действий. Но о прерываниях в следующей части.

Когда пин работает на выход, CR1 отвечает за режим работы. Единичка — push-pull (то есть ножка может с одинаковым успехом выдавать низкий и высокий уровень), ноль — пародия на выход с открытым стоком (в оригинале «pseudo open-drain» :)) — низкий уровень выдается по-прежнему, а вместо высокого пин переключается в режим входа (в регистре DDR при этом ничего не меняется, естественно). В таком режиме удобно работать с 1-wire, или с программной реализацией I2C.

CR2, когда пин настроен на выход, отвечает за «максимальную скорость переключения». Ну это так в даташите написано, а по факту от значения в этом регистре будет зависеть крутизна фронтов. Вот так:


Желтый канал прицеплен к пину с высокой скоростью переключения (единичка в CR2), а красный — к пину с низкой. Переключение происходит одновременно (одной командой), а вот скорость нарастания напряжения оказывается разной. В итоге быстрый пин поднимается примерно на 15-20 наносекунд раньше медленного. Обычно такая маленькая разница никого не волнует, но если нужно выдавать высокочастотный сигнал с ножки МК, или надо получить сигнал с крутыми фронтами, то установка бита в CR2 решит почти все проблемы (кроме проблем с наводками на соседние дорожки от этого сигнала — при крутых фронтах они только возрастут :))

В STM8S у некоторых пинов максимальная скорость не настраивается, а задана жестко:

Имейте это ввиду, если будете с ними работать.

С устройством GPIO вроде разобрались. Теперь можно посмотреть на код из предыдущей части свежим взглядом, уже точно понимая за что отвечают все эти регистры.

Мигание светодиодом это классика, но весьма скучно. Сделаем что-то более сложное. К примеру, электронную игральную «кость»: Генератор случайной цифры. Возьмем семисегментный индикатор, на который будем по-очереди выводить цифры 0 — 9. Этот счет будет останавливаться при нажатии на кнопку, и на индикаторе останется гореть одна цифра. И так до следующего нажатия кнопки. Цифры меняются с большой скоростью, и человек, нажимающий на кнопку не может специально подгадать момент и остановить счетчик на нужной цифре.

Я использовал индикатор, установленный на PINBOARD. Сегменты у него выведены на те-же пины, где находится шина данных от ЖК дисплея, и зажигаются высоким уровнем (т.е. индикатор с общим катодом). Сегменты подключаются к порту B на МК:
B0 - D0 (Сегмент E)
B1 - D1 (D)
B2 - D2 (Точка)
B3 - D3 (C)
B4 - D4 (G)
B5 - D5 (B)
B6 - D6 (F)
B7 - D7 (A)

Потому, что порт B это вообще единственный «полный» порт в МК в корпусе TQFP32. У остальных не хватает одного или пары пинов.

Общие выводы от разрядов подключены через транзисторы к земле (для активации разряда надо подать высокий уровень, чтобы открыть транзистор) и заведены на штырьки RS, R/W и E. Нам пока нужен всего один разряд, поэтому просто подключим нужный вывод к MAIN PWR — транзистор будет постоянно открыт.


Чтобы не считать вручную коды, отвечающие за символ каждой цифры, заюзаем вот такую табличку:

//Закомментировать, если используется индикатор с общим анодом (0 = зажечь сегмент)
#define COMMON_CATHODE

// Сегмент индикатора   Пин
#define segment_A 7
#define segment_B 5
#define segment_C 3
#define segment_D 1
#define segment_E 0
#define segment_F 6
#define segment_G 4
#define segment_DP 2


#ifdef COMMON_CATHODE
extern const char numbers[10] = {
/*0*/ (1 << segment_F) | (1 << segment_E) | (1 << segment_D) | (1 << segment_C) | (1 << segment_B) | (1 << segment_A),
/*1*/ (1 << segment_C) | (1 << segment_B),
/*2*/ (1 << segment_D) | (1 << segment_E) | (1 << segment_G) | (1 << segment_B) | (1 << segment_A),
/*3*/ (1 << segment_G) | (1 << segment_D) | (1 << segment_C) | (1 << segment_B) | (1 << segment_A),
/*4*/ (1 << segment_G) | (1 << segment_F) | (1 << segment_C) | (1 << segment_B),
/*5*/ (1 << segment_G) | (1 << segment_F) | (1 << segment_D) | (1 << segment_C) | (1 << segment_A),
/*6*/ (1 << segment_G) | (1 << segment_F) | (1 << segment_E) | (1 << segment_D) | (1 << segment_C) | (1 << segment_A),
/*7*/ (1 << segment_C) | (1 << segment_B) | (1 << segment_A),
/*8*/ (1 << segment_G) | (1 << segment_F) | (1 << segment_E) | (1 << segment_D) | (1 << segment_C) | (1 << segment_B) | (1 << segment_A),
/*9*/ (1 << segment_G) | (1 << segment_F) | (1 << segment_D) | (1 << segment_C) | (1 << segment_B) | (1 << segment_A)

#define DecimalPoint (1<<segment_DP)
#define ClearDisplay 0
#else
extern const char numbers[10] = {
/*0*/ ~((1 << segment_F) | (1 << segment_E) | (1 << segment_D) | (1 << segment_C) | (1 << segment_B) | (1 << segment_A)),
/*1*/ ~((1 << segment_C) | (1 << segment_B)),
/*2*/ ~((1 << segment_D) | (1 << segment_E) | (1 << segment_G) | (1 << segment_B) | (1 << segment_A)),
/*3*/ ~((1 << segment_G) | (1 << segment_D) | (1 << segment_C) | (1 << segment_B) | (1 << segment_A)),
/*4*/ ~((1 << segment_G) | (1 << segment_F) | (1 << segment_C) | (1 << segment_B)),
/*5*/ ~((1 << segment_G) | (1 << segment_F) | (1 << segment_D) | (1 << segment_C) | (1 << segment_A)),
/*6*/ ~((1 << segment_G) | (1 << segment_F) | (1 << segment_E) | (1 << segment_D) | (1 << segment_C) | (1 << segment_A)),
/*7*/ ~((1 << segment_C) | (1 << segment_B) | (1 << segment_A)),
/*8*/ ~((1 << segment_G) | (1 << segment_F) | (1 << segment_E) | (1 << segment_D) | (1 << segment_C) | (1 << segment_B) | (1 << segment_A)),
/*9*/ ~((1 << segment_G) | (1 << segment_F) | (1 << segment_D) | (1 << segment_C) | (1 << segment_B) | (1 << segment_A))

#define DecimalPoint ~(1<<segment_DP)
#define ClearDisplay 255
#endif
};


Сначала идут дефайны, определяющие в каком порядке сегменты индикатора подключены к выводам МК. Там же есть дефайн, определяющий тип индикатора («общий анод» или «общий катод»). В зависимости от них, далее, создается массив numbers[], содержащий коды цифр от 0 до 9. Для того, чтобы вывести цифру на индикатор, нужно просто взять соответствующий элемент массива и вывести в порт.

Эта табличка, чтобы не забивать main, у меня выделена в отдельный файл 7seg_table.h, который валяется в папке с проектом в подпапке inc. В IAR для подключения файла к проекту его сначала надо добавить в список на панели workspace (слева там). Щелкаем на ней правой кнопкой и выбираем Add -> Files… Добавляем наш файл. Вообще для .h файлов такое делать не обязательно (мы же не подключали таким образом iostm8l151k6.h) — это необходимо только для .c файлов, содержащих исполняемый код.

Если попробовать сейчас собрать проект, то компилятор пожалуется, что знать не знает где этот ваш 7seg_table.h лежит. По-умолчанию он ищет подключенные файлы в папке с заголовочными файлами IAR и в папке с самим проектом. А у нас он лежит в /inc. Для того, чтобы объяснить компилятору где искать файлы, идем в настройки проекта и выбираем там раздел C/C++ Compiler, а в нем вкладку Preprocessor. На ней, в поле Additional include directories прописываем путь к нашей папке с заголовочным файлом. Писать лучше с использованием переменных окружения IAR, чтобы проект потом можно было без проблем собрать на другом компьютере и/или из другой папки: "$PROJ_DIR$/inc".

$PROJ_DIR$ IAR при поиске файла IAR заменит на путь до папки с проектом.

Теперь цепляем файл к main.c и обьявляем в нем наш массив с цифрами
#include "7seg_table.h" 
extern const char numbers[10];

Порт B мы полностью задействуем для работы с индикатором, значит, все его пины надо настроить на выход
PB_DDR = 0xFF;

Сегменты индикатора зажигаются высоким уровнем, а пины по-умолчанию работают в режиме открытого стока и выдать высокий уровень не могут. Поэтому, поднимаем все биты в PB_CR1, чтобы перевести порт в режим push-pull:
PB_CR1 = 0xFF;

А еще нам понадобится кнопка. На PINBOARD их целая куча, но три можно легко набросить на выводы процессорного модуля, обозначенные BTN1, BTN2 и BTN3. BTN2 кнопка подключается к пину D6 — вот ее и используем.

Пин для кнопки должен быть настроен как вход с подтягивающим резистором. То есть в Px_DDR должен быть ноль (там и так после сброса 0x00), а в Px_CR1 — единичка.
PD_CR1_bit.C16 = 1;

С настройкой закончили, пишем основной цикл
while(1)
 {
  PB_ODR = ClearDisplay; //Сбрасываем все пины порта B - индикатор тухнет
  SomeDelay();

  //Цикл будет выполняться до тех пор, пока не нажмут кнопку (тогда 6й бит регистра IDR станет = 0)
  while (PD_IDR_bit.IDR6 == 1)
  {
   value++; //Инкрементируем счетчик
   if (value==10) value=0; //Проверяем - не ушел ли он за предел (индикатор у нас одноразрядный)
   PB_ODR = numbers[value];   //Выводим число на индикатор
   SomeDelay(); //Если не сделать задержку, то пропадет красивый эффект переключения цифр - они просто
   //сольются и будет 8 с немного разной яркостью сегментов.
  };
  SomeDelay(); //После того как нажали кнопку, делаем задержку для антидребезга
  while (PD_IDR_bit.IDR6 == 0); //Ждем, пока кнопку отпустят.
  SomeDelay(); //Опять антидребезг
  while (PD_IDR_bit.IDR6 == 1); // Ждем, пока нажмут
  SomeDelay();
  while (PD_IDR_bit.IDR6 == 0); //... и снова отпустят

  //В итоге наша шарманка будет считать цифры, пока не нажмут кнопку.
  //И продолжать счет только после повторного нажатия
 };


Можно собирать проект и запускать. Только не забудьте, если вы используете другой тип индикатора или другое подключение, поправить 7seg_table.h.

На этом пожалуй всё. В следующий раз рассмотрим прерывания от пинов (и прерывания вообще). Следите за эфиром!

← Часть 1 — Hello светодиод! Содержание Часть 3 — Прерывания →
  • +4
  • 16 декабря 2012, 18:34
  • dcoder
  • 1
Файлы в топике: 2_GPIO_7seg.zip

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

RSS свернуть / развернуть
Теперь с ЖПИО (пардон за сквернословие) все не так…

Где развернутые объяснения (другому) начанающему быдлокодеру про суммарный максимальный ток камня, про отличия HS пинов от обычных, про различия типов поддяжек на вых-вх пинов, сколько входов питания? Быстродействие, фронты, начальные емкости пинов, етс… на кой это изъяснялово с сегментниками, о чем спитч, как обозначено в топике? Ни слова про альтернативность функций выбора и самонастройку пина…
0
  • avatar
  • valio
  • 16 декабря 2012, 20:10
Ни нада его минусовать! Нет, правда, в целом valio прав. Я и сам заметил что можно было еще много чего дописать. Но заметил уже когда нажал левую мышь на кнопке публикации.

К счастью у нас тут не периодическое издание, а живые интернеты — можно послушать критику что-то дописать и исправить.

Сейчас чо-то хочу спать, поэтому вся редактура будет завтра.

Всем чмоке в этом чате
+1
Если я правильно помню, то в Кому это надо? была определена целевая аудитория. И с учетом указанного, да после прочтения поста видно, что задающий вопрос тыпо не читал (либо вступления, либо урок, либо оба сразу).
0
Половина из указанных valio вопросов — специфична для STM8, так что вполне заслуживает рассмотрения.
0
PA0 может «всосать» (sink) 80 мА, но выдать (source) только 25, как и все прочие. :)
0
  • avatar
  • Katz
  • 17 декабря 2012, 11:33
Доброго времени суток! Комент не в тему, но все же: было бы интересно, какие есть симуляторы для STM8? Несмотря на мегавозможности отладки, все-таки постоянно мучать кристалл как-то неэтично… Думаю, начинающих эта проблема очень интересует
0
… в течении года — это мин. гарантированных 166 перепрошивок ежедневно. Попробуйте наплодить хотя бы 50. Но если потратить $10 на новый комплект — жаба душит, есть RAM. Совсем все плохо, то гипотетически в IAR-е.
0
Эх… А так хотелось услышать: не тупи, скачай протэус, там уже есть поддержка…
0
Зачем, если есть отладка на живом железе, которая будет заведомо работать так, как это видно Оо
0
Ну к чему это «зачем»? Знаю, с удовольствием работаю. Но с аврами (и пиками, мать их...) привык к симулятору, вещь тоже очень приятная и наверняка не помешала бы
0
а про DMA будет??
0
Атеншн!
Я случайно немного переписал это убожество. Если это кого-то волнует — можете перечитать :) В тред реквестируется valio , который скажет, что лучше не стало :)
0
Можно дописать, что в ресете некоторые ноги подтянуты к питанию (у STM8L15X, это PB0 и PB4).
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.