Что у неё внутри.Поскольку мне достались микросхемы фирмы fairchild semiconductor: MM74HC595, то я буду опираться на её даташит.
Данная микросхема содержит 8 сдвиговых регистров с выводными переключателями. То есть содержимое этих 8 регистров данных может быть отображено в виде высокого и низкого уровня на выходах микросхемы. Так же есть бит перехода к след микросхеме (что позволяет собирать из них почти бесконечные цепочки). Предельная частота 30МГЦ. Есть почти во всех корпусах, что очень приятно услышать имея запросы в портативных устройствах(SO, TSSOP) и желание собирать устройства на макетной плате(PDIP). Распиновка у этой микросхемы не зависит от производителя:
Немного об электрических характеристиках.
Низкий входной ток в 1мА, — позволил подключить её без буферных резисторов к любым микроконтроллерам.
Высокий выходной ток в 80мА позволяет подключать не только микросхемы, но и светодиоды, зуммеры, и прочие устройства, вплоть до небольших моторчиков.
Широкий диапазон рабочих напряжений(от 2 до 6 В). Меня этот факт просто поразил, так как сейчас планирую сделать автономное устройство с особо низким потреблением питания, с общей шиной питания 2В. Пока часть, в которой используется данная микросхема работает, и сильно упростило мне задачу.
Очень удобные фишки данной микросхемы: регистр вывода и отдельный регистр сброса данной микросхемы. Они легко находятся из логической схемы:
Отладка в эмуляторе.
Немаловажным этапом работы является не только сделать, и забыть как сделано, а разобраться в том, почему и как это работает. Вот тут я вспомнил что давно ничего не делал в Протеусе, и решил замутить пробник с кнопками в нём. Данная микросхема в этом эмуляторе есть, так что настройка работы остаётся в том, чтобы просто раскидать по полю компоненты и соединить их проводниками. У меня получилась вот такая схема:
Замечания: в протеусе схема не умеет работать в режиме 3В, только 5В и выше. Поэтому эмулируйте на 5-ти вольтах, а потом 1 к 1-му переносим на 3-х волтную логику. Резисторы я добавил просто для сброса, о нём не стоит забывать. Если подключаете к мк, то есть замечательную таблицу логических уровней:
На вход нужно подавать импульсы, поэтому тактирование и не забывайте про сброс. Данная микросхема совместима с интерфейсом SPI, что значительно увеличивает максимальную частоту тактирования.
Из следующей фотографии из даташита следует, что нужна небольшая задержка после устрановки бита данных перед CLocK. Это отображено в программном коде.
(там где решётка, неустановленно)
Отладка на практике.
Эмулятор эмулятором, а на натуре проверить хотелось, вот я и собрал простую плату:
На ней особо ничего нет, но можно удобно впихнуть в макетку (к сожалению в магазине были только so-корпуса, и я не нашёл ни одного дипа). Как она легко вставляется в макетку модно посмотреть на первом изображении в этой статье.
Её можно легко заменить макеткой собранной по такой схеме:
На неё 5 разъёмов: 3 канала + питание.
Далее её подключаем к стму при помощи моего разъёма на 4 линейных разъёма(3 порта + земля), это можно видеть на первой фотографии. Подключаем к РЕ7, РЕ 9, РЕ11. Также нам потребуется UART-USB. Его я подключил по тому же разъёму, что и в прошлой статье.
1. Настройка usart.
2. Настройка функция вывода информации.
3. Настройка прерываний по событию в UARTe
Чтобы немного сократить объём статьи я просто выложу полный код, он простой, и у вас не возникнет проблем при его разборе, так как я всё комментировал.
#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_usart.h"
char uart_data;//принятые данные по USART
int i;//variables
//Функция отправляет байт в UART
void send_to_uart(uint8_t data)
{
while(!(USART2->SR & USART_SR_TC));
USART2->DR=data;
}
//Функция отправляет строку в UART, по сути пересылая по байту в send_to_uart
void send_str(char * string)
{
uint8_t i=0;
while(string[i])
{
send_to_uart(string[i]);
i++;
}
}
//Инициализируем USART2
void usart_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //Структура содержащая настройки порта
USART_InitTypeDef USART_InitStructure; //Структура содержащая настройки USART
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //Включаем тактирование порта A
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //Включаем тактирование порта USART2
GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2); //Подключаем PA3 к TX USART2
GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2); //Подключаем PA2 к RX USART2
//Конфигурируем PA2 как альтернативную функцию -> TX UART.
Подробнее об конфигурации можно почитать во втором уроке.
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//Конфигурируем PA2 как альтернативную функцию -> RX UART.
Подробнее об конфигурации можно почитать во втором уроке.
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_StructInit(&USART_InitStructure); //Инициализируем UART с дефолтными
настройками: скорость 9600, 8 бит данных, 1 стоп бит
USART_Init(USART2, &USART_InitStructure);
USART_Cmd(USART2, ENABLE); //Включаем UART
}
int main(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //Структура содержащая настройки порта
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); //Включаем тактирование порта E
GPIO_InitStructure.GPIO_Pin = (GPIO_Pin_7| GPIO_Pin_9| GPIO_Pin_11);//Выбераем нужные
выводы
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;//задаём тактовую частоту порта
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //Включаем режим выхода
GPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_UP; //подтяжка порта к + питания
GPIO_Init(GPIOE, &GPIO_InitStructure); //вызов функции инициализации
usart_init(); //Инициализируем UART
//Настраиваем прерывания по приему
__enable_irq(); //Глобальное включение прерывания
NVIC_EnableIRQ(USART2_IRQn); //Включаем прерывания от UART
NVIC_SetPriority(USART2_IRQn, 0); //Прерывание от UART, приоритет 0, самый высокий
USART2->CR1 |= USART_CR1_RXNEIE; //Прерывание по приему
GPIO_SetBits(GPIOE,GPIO_Pin_7| GPIO_Pin_9| GPIO_Pin_11);
while(1)
{
}
}
void set_one()
{
GPIO_SetBits(GPIOE,GPIO_Pin_11);
i=0;
while(i<100){i++;}
GPIO_SetBits(GPIOE,GPIO_Pin_7);
}
void set_zero()
{
GPIO_SetBits(GPIOE,GPIO_Pin_7);
}
void show()
{
GPIO_SetBits(GPIOE,GPIO_Pin_9);
}
void USART2_IRQHandler (void)
{
if (USART2->SR & USART_SR_RXNE) //Проверяем, прило ли чтонибудь в UART
{
USART2->DR = USART2->DR; //Echo по приему, символ отправленный в
консоль вернется
uart_data= USART2->DR;
GPIO_ResetBits(GPIOE,GPIO_Pin_7| GPIO_Pin_9| GPIO_Pin_11);
i=0;
while(i<100){i++;}
switch(uart_data)
{
case '1':set_one();break;
case '2':set_zero();break;
case '3':show();break;
}
i=0;
while(i<100){i++;}
}
}
В результате через UART отправляя соответствующие цифры, можно управлять выводом данной микросхемы.
Коды управления через терминал, отсылать без признака конца строки:
1. Вывести 1 в буфер (сдвинуть и добавить горящий светодиод)
2. Вывести 0 в буфер (сдвинуть и добавить не горящий светодиод)
3. Вывести на светодиоды.(да, выводить тоже можно по отдельной команде, и до момента пока вы туда не послали импульс там будет предыдущая картинка. Весьма полезная штука, если вы занимаетесь динамической индикацией.)
В итоге я записал вот это видео:
Практика на закрепление материала.
Но на этом я не остановился, и так как я видел классную статью на английском по этой штуке, но от NXP, то я решил реализовать программу, а которой там говорил автор, но на STM32f407vg, а не на ATMega8, как в примере. Там есть некоторые особенности, но они объяснены в коде.
В качестве практики на закрепление сделаем популярную и простую программу: бегущий огонёк. Смысл такой:
1. Устанавливает один бит на 1
2. Сдвигаем горящий светодиод, пока он не достигнет крайнего положения и не потухнет.
3. Переходим к пункту 1.
Используя возможности стм32, задача крайне простая. И я быстро написал вот такой код:
#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
int i,j;//variables for loop and move
void set_one()
{
GPIO_SetBits(GPIOE,GPIO_Pin_11);
i=0;
while(i<100){i++;}
GPIO_SetBits(GPIOE,GPIO_Pin_7);
GPIO_ResetBits(GPIOE,GPIO_Pin_7| GPIO_Pin_9| GPIO_Pin_11);//готовим для
следующей команды
}
void set_zero()
{
GPIO_SetBits(GPIOE,GPIO_Pin_7);
GPIO_ResetBits(GPIOE,GPIO_Pin_7| GPIO_Pin_9| GPIO_Pin_11);//готовим для
следующей команды
}
void show()//отображает данные в сдвиговом регистре
{
GPIO_SetBits(GPIOE,GPIO_Pin_9);
GPIO_ResetBits(GPIOE,GPIO_Pin_7| GPIO_Pin_9| GPIO_Pin_11);//готовим для
следующей команды
}
int main(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //Структура содержащая настройки порта
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); //Включаем тактирование порта E
GPIO_InitStructure.GPIO_Pin = (GPIO_Pin_7| GPIO_Pin_9| GPIO_Pin_11); //Выбераем
нужные выводы
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;//задаём тактовую частоту порта
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //Включаем режим выхода
GPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_UP; //подтяжка порта к + питания
GPIO_Init(GPIOE, &GPIO_InitStructure); //вызов функции инициализации
GPIO_ResetBits(GPIOE,GPIO_Pin_7| GPIO_Pin_9| GPIO_Pin_11);//готовим для команды
while(1) //бесконечный цикл работы
{
j=0;
set_one();//устанавливаем бит готорый будет гореть
show(); //выводим данные на светодиоды
while(j<8)//тут хитрость, мы экономим код, но лишный раз прогоняем цикл
{
i=0;
while(i<100000){i++;}//задержка
set_zero();//сдвигаем на 1 бит, и заполняем следующий пустым
show(); //выводим данные на светодиоды
j++; //переводим счётчик к след положению
}
}
}
Работу данной программы можно посмотреть на следующем видео: