- Лоботрясы

Поиск
Перейти к контенту

Главное меню:

Статьи по STM32

Читать в Яндекс.Подписках



    STM32. Дрыгаем ножками (пример работы: STM32+LCD+PIR sensor). Часть 2
Просмотров: 5437
     В этой части рассмотрим наиболее востребованные для работы функции библиотеки GPIO и! Наконец-то! Рассмотрим пример работы с микроконтроллером STM32 (естественно на базе STM32F050F4P6 (STM32F031F4P6))!
     Ладно, меньше эмоций. В прошлом примере можно было ознакомиться с процедурой конфигурации портов ввода-вывода (GPIO), после чего возникает вполне предсказуемый интерес к управлению этими самыми портами. Для этого в библиотеке GPIO в CooCox можно найти такие функции:
 
uint8_GPIO_ReadInputDataBit(GPIO_TypeDef *GPIOxuint16_GPIO_Pin) – возвращает логическое состояние определённого вывода (пина) порта GPIO, что для любителей AVR можно реализовать самому как GPIOx->IDR GPIO_Pin (надеюсь ещё помните, что IDR – это входной регистр);
uint16_GPIO_ReadInputData(GPIO_TypeDef *GPIOx) – возвращает состояние всего порта GPIOx (где x – это А, В, С, или F); 
uint8_GPIO_ReadOutputDataBit(GPIO_TypeDef *GPIOxuint16_GPIO_Pin) – считывает указанный бит порта GPIO (можно опросить самому как GPIOx->ODR & GPIO_Pin);
uint16_GPIO_ReadOutputData(GPIO_TypeDef *GPIOx) – возвращает содержимое всего выходного регистра GPIOx_ODR выбранного порта GPIOx;
void GPIO_SetBits(GPIO_TypeDef *GPIOxuint16_GPIO_Pin) – эта функция устанавливает выбранный бит в регистре данных GPIOx_ODR порта GPIOx в '1'. Эта побитовая установка (через регистр GPIOx_BSRR) является атомарной!;
void GPIO_ResetBits(GPIO_TypeDef *GPIOxuint16_GPIO_Pin) - устанавливает выбранный бит порта GPIOx в '0' (установка также является атомарной);
void GPIO_WriteBit(GPIO_TypeDef *GPIOxuint16_GPIO_PinBitAction BitVal) – устанавливает или сбрасывает выбранные биты порта GPIOx. Эта функция просто объединяет возможности функций GPIO_SetBits() и GPIO_ResetBits(). В качестве значения BitVal принимает параметры Bit_RESET (сброс порта) и Bit_SET (соответственно, установка в '1';
void GPIO_Write(GPIO_TypeDef *GPIOxuint16_PortVal) – выполняет запись данных PortVal в порт GPIOx (что, в принципе, представляет собой - GPIOx->ODR = PortVal); 
     Есть ещё полезная функция, присваивающая определённую альтернативную функцию (указанную пользователем) выбранному выводу порта:
void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF);
     Но описывать её не стану, т.к. она имеет оччень много возможных параметров для конфигурации вывода, не поленитесь лучше посмотреть сами в Репозитории (Repository).
 
     Подытожив, имеем неплохой и очень удобный инструментарий для работы с портами, особенно радует возможность атомарной побитовой установки, что позволяет вместо привычных нам (знатоки AVR поймут) операций типа:
GPIOА->ODR|=(1<<2)|(1<<4);
GPIOА->ODR&=~((1<<3)|(1<<5));
воспользоваться функциями GPIO_SetBits(), GPIO_ResetBits() или же GPIO_WriteBit().
 
     А вот теперь пример работы с портами!
     Данный пример, в отличие от осточертевших примеров миганий светодиодами, несёт вполне полезную практическую информацию о работе алфавитно-символьного LCD-индикатора и PIR-датчика с STM32 (такой вот винегрет!). О жидкокристаллических индикаторах, я думаю, вы слышали уже давно и предостаточно, а вот PIR-датчик (или PIR-сенсор) появился в свободном доступе (имеется ввиду, для радиолюбительских поделок) сравнительно недавно (спасибо товарищам-китайцам), поэтому вначале уделю немного внимания ему.
     PIR-сенсор – это инфракрасный датчик движения. Описывать принцип работы чувствительного элемента этого датчика не вижу смысла (а инет для чего?!), т.к. нас интересует его конкретное применение. Данный датчик является основой большинства простых систем контроля и защиты от проникновения всяких любопытных личностей на охраняемый объект. Благодаря его дешевизне и доступности, сей датчик может послужить вам основой системы умный дом или комната, радуя вас автоматическим включением света в туалете.
     Вот это чудо инженерной мысли (называется данный датчик - HC-SR501):
     Этот датчик имеет широкий диапазон напряжений питания – 4,5-20В, мизерное потребление – менее 60 мкА, рабочую зону в 140 градусов, а также возможность настройки времени присутствия сигнала на выходе датчика в пределах 5-200 секунд (это при обнаружении человечка) и изменить чувствительность (зона обнаружения) в пределах 3-7 метров. 
     Пример работы с портами, который я представляю на Ваш суд чуть ниже, просто опрашивает сигнальный выход PIR-датчика, и, в зависимости от его состояния, выводит на LCD слова PRESENT или NO PRESENT. В качестве LCD-индикатора применён четырёхстрочный алфавитно-символьный LCD-индикатор WH2004с системой команд HD44780.
     Сама схема:
     Теперь небольшая программка...

#include <stdint.h>
#include <system_stm32f0xx.h>
#include <stm32f0xx_gpio.h>
#include <stm32f0xx_rcc.h>

#define    SYSCLK 48 //Необходимо для работы функций delay_us() и delay_ms()
//#define   _USE_8_DATA_BUS //Если закомментировано - 4-х разрядная шина данных

//Вывод, к которому подключён PIR-датчик
#define PIR_SENSOR       GPIOB, GPIO_Pin_1

// Номера выводов к которым подключены управляющие выводы ЖКИ
#define LCD_PORT GPIOA
#define RCC_LCD_PORT      RCC_AHBPeriph_GPIOA // RCC_AHBPeriph_GPIOB, RCC_AHBPeriph_GPIOC...
#define RS     GPIO_Pin_0 // Вывод команда/данные
#define EN     GPIO_Pin_1 // Вывод синхронизации (тактовый)

// Номера выводов к которым подключена шина данных и управляющие выводы ЖКИ
#ifdef    _USE_8_DATA_BUS
#define   DB0   GPIO_Pin_n
#define   DB1   GPIO_Pin_n
#define   DB2   GPIO_Pin_n
#define   DB3   GPIO_Pin_n
#endif
#define   DB4   GPIO_Pin_2
#define   DB5   GPIO_Pin_3
#define   DB6   GPIO_Pin_4
#define   DB7   GPIO_Pin_5

//Адреса строк ЖКИ
const unsigned char array_LCD[]={0x80, 0xC0, 0x94, 0xD4};

GPIO_InitTypeDef     GPIO_InitStruct;

// Прототипы функций
void PIR_setup(void);
inline void delay_us(unsigned int t);
inline void delay_ms(unsigned int t);
void LCD_port_init(void);
void LCD_init(void);
void LCD_send_byte (unsigned char data);
void LCD_com(unsigned char data);
void LCD_dat(unsigned char data);
void LCD_init(void);
void LCD_puts(char *p);
void LCD_gotoxy(char row, char col);
void LCD_clear(void);
//===============================================================
//                                               Инициализация PIR-датчика
//===============================================================
void PIR_setup(void)
{
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1; //Мы хотим сконфигурировать PB1
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN; //Делаем PB1 входом
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_1; //Устанавливаем скорость работы порта
    GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; //Двухтактный выход
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP ; // Привязка к '+' питания
    GPIO_Init(GPIOB, &GPIO_InitStruct);
}
//===============================================================
//                                                  Задержка времени в мкс
//===============================================================
inline void delay_us(unsigned int t)
{
    unsigned long i;
    i = t*SYSCLK;
    while(i--);
}
//===============================================================
//                                                  Задержка времени в мс
//===============================================================
inline void delay_ms(unsigned int t)
{
    unsigned long i;
    i = t*SYSCLK*100;
    while(i--);
}
//===============================================================
//                                                Инициализация портов
//===============================================================
void LCD_port_init(void)
{
    RCC_AHBPeriphClockCmd(RCC_LCD_PORT, ENABLE); //Включаем тактирование порта
    GPIO_InitStruct.GPIO_Pin = ( RS | EN | DB4 | DB5 | DB6 | DB7 ); //Какие выводы конфигурируем
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; //Выход
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_1; //Устанавливаем скорость работы модуля GPIO
    GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; //Двухтактный выход
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; //Без подтягивающих резисторов
    GPIO_Init(LCD_PORT, &GPIO_InitStruct);
}
//===============================================================
//                                                       Передача команды
//===============================================================
void LCD_com(unsigned char data)
{
    GPIO_ResetBits(LCD_PORT, RS);
    LCD_send_byte (data);
}
//===============================================================
//                                                       Передача данных
//===============================================================
void LCD_dat(unsigned char data)
{
    GPIO_SetBits(LCD_PORT, RS);
    LCD_send_byte (data);
}
//===============================================================
//                                                       Передача байта
//===============================================================
void LCD_send_byte (unsigned char data)
{
    #ifdef    _USE_8_DATA_BUS
    GPIO_SetBits(LCD_PORT, EN);
    if(data&1) GPIO_SetBits(LCD_PORT, DB0); else GPIO_ResetBits(LCD_PORT, DB0);
    if(data&2) GPIO_SetBits(LCD_PORT, DB1); else GPIO_ResetBits(LCD_PORT, DB1);
    if(data&4) GPIO_SetBits(LCD_PORT, DB2); else GPIO_ResetBits(LCD_PORT, DB2);
    if(data&8) GPIO_SetBits(LCD_PORT, DB3); else GPIO_ResetBits(LCD_PORT, DB3);
    if(data&16) GPIO_SetBits(LCD_PORT, DB4); else GPIO_ResetBits(LCD_PORT, DB4);
    if(data&32) GPIO_SetBits(LCD_PORT, DB5); else GPIO_ResetBits(LCD_PORT, DB5);
    if(data&64) GPIO_SetBits(LCD_PORT, DB6); else GPIO_ResetBits(LCD_PORT, DB6);
    if(data&128) GPIO_SetBits(LCD_PORT, DB7); else GPIO_ResetBits(LCD_PORT, DB7);
    __NOP();
    GPIO_ResetBits(LCD_PORT, EN);
delay_us(40);
    #else
    GPIO_SetBits(LCD_PORT, EN);
    if(data&16) GPIO_SetBits(LCD_PORT, DB4); else GPIO_ResetBits(LCD_PORT, DB4);
    if(data&32) GPIO_SetBits(LCD_PORT, DB5); else GPIO_ResetBits(LCD_PORT, DB5);
    if(data&64) GPIO_SetBits(LCD_PORT, DB6); else GPIO_ResetBits(LCD_PORT, DB6);
    if(data&128) GPIO_SetBits(LCD_PORT, DB7); else GPIO_ResetBits(LCD_PORT, DB7);
    __NOP();
    GPIO_ResetBits(LCD_PORT, EN);
    delay_us(40);
    GPIO_SetBits(LCD_PORT, EN);
    if(data&1) GPIO_SetBits(LCD_PORT, DB4); else GPIO_ResetBits(LCD_PORT, DB4);
    if(data&2) GPIO_SetBits(LCD_PORT, DB5); else GPIO_ResetBits(LCD_PORT, DB5);
    if(data&4) GPIO_SetBits(LCD_PORT, DB6); else GPIO_ResetBits(LCD_PORT, DB6);
    if(data&8) GPIO_SetBits(LCD_PORT, DB7); else GPIO_ResetBits(LCD_PORT, DB7);
    __NOP();
    GPIO_ResetBits(LCD_PORT, EN);
    delay_us(40);
    #endif
}
//===============================================================
//                                                     Инициализация ЖКИ
//===============================================================
void LCD_init(void)
{
    delay_ms(20);
    LCD_com(0x33); delay_ms(7);
    LCD_com(0x32); delay_us(100);
    LCD_com(0x28); LCD_com(0x08);
    LCD_com(0x01); delay_ms(20);
    LCD_com(0x06);
    LCD_com(0x0D);
}
//===============================================================
//                                                          Вывод строки
//===============================================================
void LCD_puts(char *p)
{
    while(*p) LCD_dat(*p++);
}
//===============================================================
//                                              Установка позиции курсора
//===============================================================
void LCD_gotoxy(char row, char col)
{
    LCD_com(array_LCD[row]+col);
}
//===============================================================
//                                                       Очистить дисплей
//===============================================================
void LCD_clear(void)
{
    LCD_com(0x01);
    delay_ms(2);
}
//===============================================================

int main(void)
{
    // Инициализация системы тактирования
    SystemInit();
    LCD_port_init();

    LCD_init();
    LCD_com(0x0C); //Выключение курсора
    LCD_gotoxy(0, 0);
    LCD_puts("STM32 - fantastisch!"); //Личное мнение об изучаемом девайсе...
    LCD_gotoxy(1, 1);
    LCD_puts("made by Igoryosha"); //Не забыть, естественно, себя любимого...
    LCD_gotoxy(2, 8);
    LCD_puts("for");
    LCD_gotoxy(3, 3);
    LCD_puts("LOBOTRYASY.NET");
    delay_ms(1000);
    LCD_clear();

  while(1)
  {
     if(GPIO_ReadInputDataBit(PIR_SENSOR) == 1) //Если есть движение - срабатывает PIR-датчик
     {
         LCD_gotoxy(2, 4);
         LCD_puts("PRESENT ");
     }
     else //Иначе - не срабатывает 
     {
         LCD_gotoxy(2, 4);
         LCD_puts("NO PRESENT");
     }
     delay_ms(10);
   }
}
     И ведь совсем не страшно, правда?

     Все это вышеприведенное добро прекрасно работает в железе, о чём можно убедиться по фотографиям (выкладывать видео не вижу смысла - это всё равно что нажимать кнопочку и смотреть как работает светодиодик). Микроконтроллер ATmega32 ко всему происходящему не имеет никакого отношения (просто проходил мимо), просто взял отладочную плату с ним ради установленного жидкокристаллического индикатора, такая вот сборная солянка.
     Если вы создали по аналогии свой проект, то для того, чтобы его скомпилировать, необходимо нажать в меню программы CooCox кнопочку Build (или F7).
     А как теперь загрузить программу в контроллер? Очень просто - берём программатор ST-LINK, соединяем между собой проводочками линии SWDAT и SWCLK (это линии интерфейса SWD) на отладочной плате и на программаторе, после чего (надеюсь, вы уже выбрали в настройках CooCox программатор ST-LINK) находим в меню кнопку Download Code to Flash и жмём!
     Нюанс для тех, кто сидя без движения перед PIR-датчиком недоумевает, почему датчик на выходе ничего не выдаёт: датчик реагирует на изменение электромагнитного (теплового) излучения, т.е. на движение!
     И ещё! Очень важное примечание! Если вы хотите использовать выводы PA13 и PA14, являющиеся выводами интерфейса SWD (по которому выполняется программирование микроконтроллера) в качестве обычных вводов-выводов, то после конфигурации их в программе в качестве GPIO и записи прошивки в микроконтроллер, дальнейший доступ к микроконтроллеру в обычном режиме будет невозможен. Что же вы хотели?! Мы переконфигурировали эти выводы!    
     Поэтому, чтобы снова получить доступ к памяти микроконтроллера и иметь возможность его перепрограммировать, необходимо воспользоваться программой STM32 ST-LINK Utility (которая обеспечивает работу программатора/отладчика ST-Link и которую вы скорее всего уже скачали ранее).
     Открыв программу (и подключив, естественно, программатор ST-Link с программируемым микроконтроллером), зайдите в меню Target >  Settingsи выберите в окошечке Mode режим Connect Under Reset
     После этого клацните мышью OK и нажмите (ручками!) на плате с микроконтроллером кнопочку RESET и отпустите её. Ура, микроконтроллер подключён! А дальше вы уже можете стереть старую прошивку (Target > Erase Chip) и программировать свой контроллер обычным путём через CooCox, или тут же запрограммировать его новой прошивкой (Target > Program...).
 
 
Назад к содержимому | Назад к главному меню