STM32 — GPIO. Первая программа

Итак, я уже достаточно наигрался с STM32, чтобы потихоньку начать писать статьи про него. В этой статье я рассмотрю работу с GPIO (входы/выходы общего назначения). Это основа для работы со всей остальной периферией.

На моей отладочной плате установлен процессор STM32F103VET6 в корпусе LQFP100. Он имеет 80 GPIO выводов. Они имеют следующие обозначения:
  • PA0..PA15;
  • PB0..PB15;
  • PC0..PC15;
  • PD0..PD15;
  • PE0..PE15.
Если вывод предполагается использовать как самостоятельный вход/выход, то можно использовать практически любой из имеющихся. Соответствие пинам микропроцессора можно посмотреть в даташите:
Конечно это нам пригодится, если мы самостоятельно разводим плату под процессор. В нашем же случае все необходимые выводы выведены на контактные гребёнки отладочной платы.
Вообще, очень часто придётся обращаться к даташиту на микропроцессор, так как там можно найти много полезной информации. Скачать его можно на официальном сайте STMicroelectronics.
Каждому выводу можно установить определённый режим работы. У ARM их аж 8:
РежимФункцияПримечание
GPIO_Mode_AIN
(Analog Input)
ВходАналоговый
GPIO_Mode_IN_FLOATING
(Input float)
ВходПлавающий (без подтяжки)
GPIO_Mode_IPD
(Input Pull-down)
ВходПодтяжка к земле
GPIO_Mode_IPU
(Input Pull-up)
ВходПодтяжка к питанию
GPIO_Mode_Out_OD
(Output Open Drain)
ВыходС открытым стоком
GPIO_Mode_Out_PP
(Output Push-Pull)
ВыходДвухтактный
GPIO_Mode_AF_OD
(Alternate Function Open Drain)
Альтернативная функцияС открытым стоком
GPIO_Mode_AF_PP
(Alternate Function Push-Pull)
Альтернативная функцияДвухтактный
Режим альтернативной функции позволяет использовать вывод для внутренней периферии (например, как USART вход/выход). Соответствие выводов функциям можно посмотреть в том же даташите, в таблице №5.
Первые 6 столбцов Pins показывают соответствие ногам микропроцессора в разных корпусах.
Pin name — название пина.
Type — тип: I — вход, O — выход, S — питание.
I/O Level — уровень сигнала: FT означает, что данный пин позволяет подавать на себя 5 вольт. Иначе — не более 3.3 вольт.
Main function — главная функция, которая устанавливается на пине после включения/сброса.
Alternate functions: default — альтернативная функция вывода.
Alternate functions: remap — если по каким-то причинам вы не можете использовать функцию на её «родном» пине, то функцию можно «заремапить» на другой пин. В этом столбце как раз указывается дополнительная функция после ремапа.

Например, рассмотрим контакт PC10:
Он находится на 78 пине чипа (корпус LQFP100), может работать как вход/выход, толерантен к 5 вольтам, при необходимости его можно использовать как TX-вывод UART4 или сделать ремап для TX USART3.
Ещё, что куда, можно посмотреть на следующей картинке (фулсайз):

Есть ещё замечательная программа MicroXplorer, которая разрабатывается самой STMicroelectronics. В ней можно посмотреть конфигурацию всех пинов, увидеть если что-то мешается друг другу и, более того, создать код для конфигурации портов (об этом чуть позже).

Задача 1
Попробуем просто помигать светодиодами, которые находятся на плате.
Для начала посмотрим, куда они подключены:

Итак, это пины PC6, PC7, PD13 и PD6. Будем мигать светодиодами по-очереди. Для начала необходимо включить тактирование используемых портов, без этого ничего работать не будет. Для этого заведём функцию RCC_Configuration. Сразу же там вызовем функцию SystemInit для начальной инициализации микропроцессора и установки частот.
1
2
3
4
void RCC_Configuration(void)
{
  SystemInit();
}

Теперь собственно включаем тактирование. Для этого сначала рассмотрим следующую схему:

На ней мы видим три основные шины:

  • Advanced High Performance Bus (AHB);
  • Low speed Advanced Peripherial Bus (APB1);
  • High speed Advanced Peripherial Bus (APB2).
Шины AHB и APB2 высокоскоростные и могут работать на частотах вплоть до 72 МГц. Шина APB1 медленнее, её частота до 36 МГц. Для включения тактирования устройств на шине, существуют соответственно функции RCC_AHBPeriphClockCmd, RCC_APB1PeriphClockCmd и RCC_APB2PeriphClockCmd.
По картинке видно, что GPIO порты подключены к шине APB2, поэтому будем использовать последнюю функцию.
1
2
3
4
5
6
void RCC_Configuration(void)
{
  SystemInit();
 
  RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD , ENABLE);
}
P.S.
Рекомендуют для защиты все неиспользуемые выходы переводить в режим аналогового входа (Configure all unused GPIO port pins in Analog Input mode (floating input trigger OFF), this will reduce the power consumption and increase the device immunity against EMI/EMC). Для этого просто необходимо включить тактирование для всех GPIO и включить их на вход:
1
2
3
4
5
6
7
8
9
10
11
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |
          RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD |
          RCC_APB2Periph_GPIOE, ENABLE);
 
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_Init(GPIOE, &GPIO_InitStructure);

Это необходимо сделать перед настройкой всех остальных пинов.

Всё, тактирование включено. Теперь необходимо настроить выводы. Для удобства это будем делать в функции GPIO_Configuration.

Объявим глобальную переменную:

GPIO_InitTypeDef GPIO_InitStructure;

И теперь, используя её, зададим параметры пина PC6:

1
2
3
4
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;         //шестой пин
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //двухтактный выход
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //скорость 50 МГц (доступны также 2 и 10)
GPIO_Init(GPIOC, &GPIO_InitStructure);            //записываем информацию в регистр

В итоге вся функция будет выглядеть так:

1
2
3
4
5
6
7
8
9
10
11
12
void GPIO_Configuration()
{
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOC, &GPIO_InitStructure);
 
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_13;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOD, &GPIO_InitStructure);
}
Теперь давайте заглянем в программу MicroXplorer. Весь код для инициализации интерфейсов можно сгенерировать с помощью него. Для этого указываем, какие пины мы хотим использовать и в каком режиме (вход/выход).

Затем, переходим на вкладку «Configuration» и жмём кнопку «GPIO». Там указываем для наших пинов скорость работы и какой именно режим выхода мы хотим получить. Затем жмём OK.

После этого выбираем в меню Tools -> Generate code. В результате, в сохранённом файле mx_gpio.c мы увидим знакомый уже код включения тактирования и инициализации пинов.

Это мы только настроили все необходимые пины! Теперь переходим к основной части программы. Вызываем ранее созданные функции:
1
2
3
4
5
int main(void)
{
  RCC_Configuration();
  GPIO_Configuration();
}
И далее, в бесконечном цикле по-очереди включаем и выключаем пины (если быть точнее, то подключаем их то к питанию, то к земле). Для этого служат функции GPIO_SetBits и GPIO_ResetBits:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int main(void)
{
  RCC_Configuration();
 
  GPIO_Configuration();
 
  while (1)
  {
    GPIO_SetBits(GPIOC, GPIO_Pin_6); //Включаем D1
    Delay(0xAFFFF);
 
    GPIO_SetBits(GPIOC, GPIO_Pin_7 ); //Включаем D2
    GPIO_ResetBits(GPIOC, GPIO_Pin_6); //Выключаем D1
    Delay(0xAFFFF);
 
    GPIO_SetBits(GPIOD, GPIO_Pin_13 ); //Включаем D3
    GPIO_ResetBits(GPIOC, GPIO_Pin_7); //Выключаем D2 и т.д.
    Delay(0xAFFFF);
 
    GPIO_SetBits(GPIOD, GPIO_Pin_6 );
    GPIO_ResetBits(GPIOD, GPIO_Pin_13);
    Delay(0xAFFFF);
 
    GPIO_ResetBits(GPIOD, GPIO_Pin_6);
  }
}

Полностью код выглядит так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include "stm32f10x.h"
 
GPIO_InitTypeDef GPIO_InitStructure;
 
void RCC_Configuration(void);
void GPIO_Configuration(void);
 
void Delay(__IO uint32_t nCount);
 
int main(void)
{
  RCC_Configuration();   
 
  GPIO_Configuration();
 
  while (1)
  {
    GPIO_SetBits(GPIOC, GPIO_Pin_6);                  
    Delay(0xAFFFF);
 
    GPIO_SetBits(GPIOC, GPIO_Pin_7 );
    GPIO_ResetBits(GPIOC, GPIO_Pin_6);
    Delay(0xAFFFF);
 
    GPIO_SetBits(GPIOD, GPIO_Pin_13 );
    GPIO_ResetBits(GPIOC, GPIO_Pin_7);
    Delay(0xAFFFF);
 
    GPIO_SetBits(GPIOD, GPIO_Pin_6 );
    GPIO_ResetBits(GPIOD, GPIO_Pin_13);
    Delay(0xAFFFF);
 
    GPIO_ResetBits(GPIOD, GPIO_Pin_6);
  }
}
 
 
void RCC_Configuration(void)
{   
  SystemInit();
 
  RCC_APB2PeriphClockCmd( RCC_APB2Periph_USART1 |RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |
                         RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD |
                         RCC_APB2Periph_GPIOE, ENABLE);
}
 
void GPIO_Configuration()
{
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOC, &GPIO_InitStructure);
 
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_13;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOD, &GPIO_InitStructure);
}
 
void Delay(__IO uint32_t nCount)
{
  for(; nCount != 0; nCount--);
}
Ссылки на архивы с проектами есть в конце статьи. Скомпилировать можно под Keil.
Теперь рассмотрим процесс прошивки. У меня пока нет программатора ST-Link, но STM32 к счастью, можно шить через UART. На моей плате для этого имеется USB интерфейс (USB-UART конвертор).
Качаем Flash Loader Demonstrator. Подключаем плату к компьютеру, перед этим перемычку BOOT0 ставим в положение 1. Это режим загрузчика. В положении 0 данная перемычка включает режим выполнения пользовательской программы сразу после включения/сброса.
Запускаем программу. Настраиваем параметры порта. Если в процессе загрузки программы возникает ошибка, попробуйте установить меньшую скорость передачи. У меня всё работает и на максимальных скоростях.
Здесь мы видим сообщение, что микросхему успешно удалось прочитать. Если это не удаётся, то значит стоит защита от чтения, которую можно снять соответствующей кнопкой, при этом текущее содержимое будет уничтожено. Функцию защиты можно использовать для защиты программы от копирования. Также здесь мы можем увидеть размер флэш-памяти микроконтроллера.
Здесь мы видим адреса страниц памяти, а также их состояние (защита от чтения/записи). Просто жмём Next.
Вот здесь уже предстоит выбрать, что мы хотим сделать с микропроцессором, а точнее с его памятью. Можно очистить память (полностью или частично), загрузить программу, выгрузить программу из памяти в файл, установить защиту, либо произвольно отредактировать данные. Выбираем загрузку на устройство (Download to device). Выбираем hex-файл с прошивкой (находится в папке Obj каталога проекта). Галочка «Verify after download» позволяет проверить правильность записи программы. Если же поставить галочку «Jump to the user program», то программа будет выполнена сразу же после загрузки, даже без возвращения перемычки BOOT0 на место и перезагрузки контроллера.


После удачной прошивки должны появиться бегущие огоньки из светодиодов на плате.

Обратите внимание, что загрузчик «намертво» прошит в STM32 и стереть его невозможно. Это делает данные микропроцессоры неубиваемыми в плане прошивки. При неудачной заливке программы, просто необходимо перепрошить её ещё раз.

Задача 2

Теперь попробуем написать программу, которая будет зажигать светодиоды при нажатии на кнопки. Светодиод D1 для кнопки S1, D2 для S2 и т.д.
Посмотрим подключение кнопок:
Они подключены соответственно на PE5, PE4, PE3, PE2. При этом, по-умолчанию пин подтянут резистором к питанию, а при нажатии на кнопку соединяется с землёй.
Включаем тактирование:
1
2
3
4
5
6
7
8
void RCC_Configuration(void)
{
  SystemInit();
 
  RCC_APB2PeriphClockCmd( RCC_APB2Periph_USART1 |RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |
                         RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD |
                         RCC_APB2Periph_GPIOE, ENABLE);
}

Настраиваем пины:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void GPIO_Configuration()
{
  //Кнопки
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //вход с подтяжкой к питанию
  GPIO_Init(GPIOE, &GPIO_InitStructure);
 
  //светодиоды D1 и D2
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOC, &GPIO_InitStructure);
 
  //светодиоды D3 и D4
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_13;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOD, &GPIO_InitStructure);
}
В основной программе будем проверять, нажата ли кнопка (на пине будет логический ноль) и в этом случае включать светодиод. Иначе — выключаем его. Выглядеть код для кнопки S1 будет так:
1
2
3
4
5
6
7
8
if (!GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_5))
{
  GPIO_SetBits(GPIOC, GPIO_Pin_6);
}
else
{
  GPIO_ResetBits(GPIOC, GPIO_Pin_6);
}

Весь код выглядит так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include "stm32f10x.h"
 
GPIO_InitTypeDef GPIO_InitStructure;
 
void RCC_Configuration(void);
void GPIO_Configuration(void);
 
void Delay(__IO uint32_t nCount);
 
int main(void)
{
  RCC_Configuration();
 
  GPIO_Configuration();
 
  while (1)
  {
    if (!GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_5))
    {
      GPIO_SetBits(GPIOC, GPIO_Pin_6);
    }
    else
    {
      GPIO_ResetBits(GPIOC, GPIO_Pin_6);
    }
 
    if (!GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_4))
    {
      GPIO_SetBits(GPIOC, GPIO_Pin_7);
    }
    else
    {
      GPIO_ResetBits(GPIOC, GPIO_Pin_7);
    }
 
    if (!GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_3))
    {
      GPIO_SetBits(GPIOD, GPIO_Pin_13);
    }
    else
    {
      GPIO_ResetBits(GPIOD, GPIO_Pin_13);
    }
 
    if (!GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_2))
    {
      GPIO_SetBits(GPIOD, GPIO_Pin_6);
    }
    else
    {
      GPIO_ResetBits(GPIOD, GPIO_Pin_6);
    }
  }
}
 
void RCC_Configuration(void)
{
  SystemInit();
 
  RCC_APB2PeriphClockCmd( RCC_APB2Periph_USART1 |RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |
                         RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD |
                         RCC_APB2Periph_GPIOE, ENABLE);
}
 
void GPIO_Configuration()
{
  //Кнопки
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //вход с подтяжкой к питанию
  GPIO_Init(GPIOE, &GPIO_InitStructure);
 
  //светодиоды D1 и D2
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOC, &GPIO_InitStructure);
 
  //светодиоды D3 и D4
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_13;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOD, &GPIO_InitStructure);
}
 
void Delay(__IO uint32_t nCount)
{
  for(; nCount != 0; nCount--);
}
Предупреждение!
Автор не несёт ответственности за возможную порчу оборудования. Всё, что вы делаете — вы делаете на свой страх и риск!

Похожие записи:

2 Комментарии “STM32 — GPIO. Первая программа

  1. Здравствуйте, пытаюсь в IAR выдать 1 на 12 ногу F407///

    int main()
    { //SystemInit();

    //RCC_AHB1PeriphClockCmd(RCC_Ahb1Periph_GPIOD, Enable);
    //RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
    GPIOD->MODER |= GPIO_MODER_MODER12_0; //output
    GPIOD->OTYPER &= ~GPIO_OTYPER_OT_12; //Output push-pull
    GPIOD->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR12_1; //10 MHz
    GPIOD->PUPDR &=~ GPIO_PUPDR_PUPDR12; //No pull-up, pull-down
    //GPIOD->ODR=0xFFFF;
    //GPIOD->BSRRL = GPIO_BSRR_BS_12;

    В коментариях варианты компиляции, никаких ошибок все хорошо, заливаю hex и ничего не происходит

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *