Урок 9
Оформление кода. Функции
Сегодня мы попытаемся немного покрасивее оформить наш код. Главное — это конечно не красота, а то, чтобы код мы в любой момент могли прочитать и понять, причем не только мы.
Для этого существует много способов оформления кода. И один из них — функции. Функции в языке C/C++ используются для того, чтобы объединить какой-то участок кода, который особенно может в любой момент повторяться, или который представляет собой какую-то логически-законченную процедуру в отдельный фрагмент и может быть в любой момент вызван из какого-то другого участка кода.
Как пишутся функции — мы в принципе знаем с самого первого занятия, но до сих пор мы использовали только одну функцию. Это была функция main(), которая является точкой входа в нашу программу. Всё выполнение кода начинается именно с этой функции. А в дальнейшем мы будем использовать очень много различных функций. Поэтому, если кто-то что-то по функциям не поймёт, то в процессе наших дальнейших занятий он обязательно возместит данный пробел за счёт огромной практики использования функций.
Кроме тела у функции также существуют аргументы, которые мы можем передавать данной функции из фрагмента кода при вызове — это входные аргументы. А также функция может возвращать один аргумент. Как правило по данному аргументу мы будем судить, как именно прошел процесс в вызываемой функции.
Сегодня мы попытаемся написать такую функцию, которая благодаря входному аргументу, будет отображать на индикаторе определённую цифру.
Сегодня я уже не буду рассказывать, как создавать проект, я думаю, это уже всем надоело. Запустим уже созданный заранее из кода прошлого занятия и целиком настроенный проект и начнём писать нашу функцию. Напишем мы её до фунции main(), так как интерпретатор команд «смотрит» код сверху вниз и наша функция будет поэтому уже ему «знакома» в коде функции main(), и будет доступна для вызова в коде (или как говорят «видна»). Можно также писать функции и после того места, откуда они вызываются, но тогда нужно будет уже писать прототип функции. С прототипами функций мы познакомимся в более поздних занятиях. А сегодня напишем функцию сразу после объявления глобальных переменных. Глобальные переменные «видны» во всех функциях файла. Вот наша функция — пока без кода, с пустым телом. Дадим ей имя segchar. Имя функции должно говорить тому, кто будет читать код о том, чем данная функция будет заниматься. В нашем случае функция будет с помощью сегментов (seg) определённые символы (char). Вот поэтому и такое название
void segchar (unsigned char seg)
{
}
У данной функции будет только один входной аргумент — это целочисленное короткое значение с именем seg. Возвращать функция ничего не будет, о чём свидетельствует значение void перед её именем, то есть мы будем «верить» функции, что она справится с выводом символа на экран, и проверять это не будем.
Также чтобы функция «понимала», какой именно зажигать символ, нам желательно изучить ещё один оператор — это оператор switch, который в зависимости от значения аргумента выполняет определённый участок кода. Это непростой оператор. Для этих целей конечно подойдёт и оператор if, но когда вариантов слишком много (цифр ведь целых 10), то удобнее всё-таки применить оператор ветвления switch. Вот что это за операор
Я думаю, несмотря на сложность данного оператора, мы с ним непременно разберёмся в процессе многочисленного его использования. Мастерство приходит с практикой.
Давайте применим данный оператор в нашей функции
void segchar (unsigned char seg)
{
switch (seg)
{
case 1: PORTD = 0b11111001; break;
case 2: PORTD = 0b10100100; break;
case 3: PORTD = 0b10110000; break;
case 4: PORTD = 0b10011001; break;
case 5: PORTD = 0b10010010; break;
case 6: PORTD = 0b10000010; break;
case 7: PORTD = 0b11111000; break;
case 8: PORTD = 0b10000000; break;
case 9: PORTD = 0b10010000; break;
case 0: PORTD = 0b11000000; break;
}
}
Давайте немного разберёмся. Здесь у нас в зависимости от значения переменной seg мы будем попадать в определённый участок кода, и будем его выполнять, пока не встретится оператор break. Как только он попадётся, то мы из тела оператора switch выйдем, ну и так как за закрывающей тело фигурной скобкой следует следующая скобка, то мы вернёмся из функции в то место, откуда мы данную функцию вызвали. Ну конечно попадать, возвращаться, выходить будем не мы, это так образно говорится. Заниматься этим будет АЛУ. Мы просто представляем себя в роли АЛУ.
Здесь уже в значениях, присваваиваемых порту D, я не стал применять инвертирование, а поменял нули на единицы и наоборот.
Перейдём в функцию main() и удалим оттуда ненужные нам комментарии
PORTB = 0b00000001; // 1 2 3 4 5 6 7 8
while(1) // 0b|dp|g|f|e|d|c|b|a|
Перед тем, как вызвать нашу функцию в бесконечном цикле, мы организуем цикл, который будет перебирать подряд цифры от 0 до 9. Код скопируем из закомментированного участка бесконечного цикла, переместив его до обработчика кнопки, а комментарий потом уберем
while(1)
{
for (i=0;i<10;i++)
{
segchar(i);
_delay_ms(500);
}
if (!(PINB&0b00000001)){
Код кнопки пока не трогаем, кнопку эту мы сегодня ещё вспомним.
Теперь соберём проект и посмотрим в протеусе. Файл протеуса также был скопирован с переименованием, и в свойствах контроллера был добавлен путь к новой прошивке. Запустим проект в протеусе и убедимся в том, что у нас циферки таким же образом работают, как и на прошлом занятии. Тем самым мы добились значительного сокращения кода в главной функции программы.
Но это ещё не всё. у нас без дела простаивает кнопка. Давайте и ей дадим какую-нибудь работу. Здесь уже одной переменной для кнопки не обойдёмся, так как нам нужен будет флаг состояния кнопки. Добавим переменную
unsigned butcount=0,butstate=0;
Также нам нужен будет ещё один цикл, но не бесконечный, как я его назвал неправильно в видеоверсии, а условный, то есть тот, который выполняется, пока выполняется условие в скобках. Данный цикл мы вставим в тело цикла for. Условием будет определённое значения флага состояния кнопки. А в тело данного цикла мы вставим весь код отслеживания состояния кнопки, который мы написали на позапрошлом занятии, когда занимались с кнопкой. Также поубираем из данного кода управление состоянием порта D, а вместо этого, если кнопка будет нажата, то мы будем обнулять счётчик. Также в обоих случаях, то есть и в случае нажатой кнопке, и в случае отжатой, мы будем устанавливать статус кнопки в единицу, чтобы выйти из нашего цикла. А после того как мы выйдем из данного цикла, мы будем вызывать функцию отображения нашей цифры, затем вызывать задержку, а затем сбрасывать статус кнопки в ноль, чтобы в следующем цикле оператора for заново попадать в цикл с обработчиком кнопки. Вот такой вот у нас теперь получится код в бесконечном цикле
while(1)
{
for (i=0;i<10;i++)
{
while(butstate==0)
{
if (!(PINB&0b00000001)){
if (butcount < 5)
{
butcount++;
}
else
{
i=0;
butstate=1;
}
}
else
{
if (butcount > 0)
{
butcount--;
}
else
{
butstate=1;
}
}
}
segchar(i);
_delay_ms(500);
butstate = 0;
}
}
Жирным шрифтом, как всегда, отмечен новый код. То есть, мы видим, что кода прибавилось немного, зато сколько получилось ветвлений. Но чем сложнее код, тем больше мы начинаем понимать в искусстве программирования. Вот теперь соберем код и проверим опять же в протеусе. Кнопка должна обнулять счётчик и счёт должен будет начинаться заново. В протеусе всё работает. Теперь прошьём контроллер и проверим на живом контроллере, живой кнопке и живом индикаторе. У нас также всё работает
Предыдущий урок Программирование МК AVR Следующий урок
Приобрести программатор USBASP USBISP с адаптером можно здесь USBASP USBISP 3.3 с адаптером
Смотреть ВИДЕОУРОК в Rhttps://rutube.ru/video/af54cd69fd13b9c2bc7a97fbfffc6e15/uTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Все получилось! Идем дальше)
Пытаюсь для АВР бензогенератора написать на attiny2313 и пока ничего не получается(((. Точнее есть схема и прошивка без исходника но там меня не все устраивает. Можете помочь похожим уроком?
Думаю, вряд ли. Так как очень мало входных данных. Да и генератора у меня нет. Без наличия реальной схемы не работаю, так как урок должен быть с полноправной практической частью.
Пишу сюда, т.к. конкретного урока по переменным не нашел. Как в Atmel Studio создать переменную unsigned int e; для _delay_ms(время)?
Если делать так
unsigned int e;
_delay_ms(40)
е=40
а потом использовать _delay_ms(e) компилятор блажит типа это не константа.
_delay_ms оперирует только константами, о чем и говорит ошибка.
Создайте, например, цикл for с нужной переменной в числе повторов, а внутрь цикла поместите _delay_ms(1)
Я спросил потому, что в CVAVR это
delay_ms(40)
е=40
а потом использовать _delay_ms(e) проходит.
Исправьте в коде «butcount—» — здесь два знака «-» заменились длинным дефисом.
У меня два минуса в редакторе. А уже в сохранённой версии в браузере тире.
Спасибо за внимательность! Сейчас попробую исправить.
В качестве чего выступает переменная butstate?
И как можно понять логику работы вот этого участка кода:
while(butstate==0)
{
if (!(PINC&0b00000001))//опрос кнопки
{
if (butcount 0)
{
butcount—;
}
else
{
butstate=1;
}
}
}
Особенно интересная работа цикла while с переменной в скобках
Дмитрий,
Переменная butstate выступает в качестве флага, который определяется, в какой мы стадии в данный момент находимся и, в зависимости от этого, мы либо инкрементируем счётчик, либо декрементируем. В скобках переменных нет. Там только регистр PINC, содержащий состояние ножке порта C на данный момент, которую мы логически умножаем на единицу, которая соответствует нулевому биту. Тем самым остальные биты в PINC очищаются и мы получаем значение только нулевого бита, а следовательно и нулевой ножки порта C. Если результат опреации будет ненулевым, то мы попадём в тело верхнего условия, а если нулевой, то не попадём.
Кажись вы перепутали butstate и butcount. Как я понял butstate для выхода из цыкла
В самом конце не хватает }
Здравствуйте! Сразу хочу поблагодарить за Ваш труд. Мне как новичку Ваши уроки очень полезные. Но у меня вопрос не по теме, а как говорится — по ходу. Подскажите пожалуйста как в AtmelStudio настроить редактор кода,чтобы код шёл лесенкой? Как говорится — на скорость не влияет, но когда вижу у людей такую форму кода, аж приятно смотреть. В MPLAB X IDE эта функция по умолчанию. Но мы сейчас работаем на Atmel, а он собака весь код гонит в оду линию. Выглядит пародийно как-то.
Не совсем понимаю смысл выражения
segchar(i);
i-это переменная меняется от 0 до 9
Но по определению void segchar (unsigned char seg)
т.е. входные аргументы это unsigned char i и unsigned char seg
может тогда нужно писать так
void segchar (unsigned char seg,unsigned char i)
{
}
Посмотрите урок по функциям в рубрике «Программирование на C» и всё прояснится.
Заменил переменную seg на i все работает.Часть кода для кнопки убрал.Для
чего нужна переменная seg?
#define F_CPU 8000000L
#include
#include
unsigned int i;
//———————————————
void segchar (unsigned int i)
{
switch (i)
{
case 1: PORTD = 0b11111001; break;
case 2: PORTD = 0b10100100; break;
case 3: PORTD = 0b10110000; break;
case 4: PORTD = 0b10011001; break;
case 5: PORTD = 0b10010010; break;
case 6: PORTD = 0b10000010; break;
case 7: PORTD = 0b11111000; break;
case 8: PORTD = 0b10000000; break;
case 9: PORTD = 0b10010000; break;
case 0: PORTD = 0b11000000; break;
}
}
//———————————————
int main(void)
{
DDRD = 0xFF;
PORTD = 0b00000000;
while(1)
{
for (i=0;i<10;i++)
{
segchar(i);
_delay_ms(500);
}
}
}