Решил написать небольшую вводную статейку для тех, кто впервые взялся за программирование микроконтроллеров и никогда раньше не был знаком с языком Си. В подробности влезать не будем, обо всем понемногу, чтобы получить общее представление по работе с CodeVisionAVR.
Более подробную информацию можно посмотреть на английском языке в CodeVision User Manual, а также рекомендую сайт http://somecode.ru с видеоуроками по Си для микроконтроллеров и книгу «Как программировать на С» автор Дейтель, это единственная годная книга, с которой я сам начинал.
Начнем с того, что какие бы действия мы не делали, в конечном счете, все сводится к прошивке микроконтроллера. Сам процесс прошивки происходит следующим образом: при помощи некой программы выбирается файл прошивки, выбираются параметры, нажимается кнопочка и происходит непосредственно прошивка, которая, по сути, является копированием. Точно также как с компьютера на флешку вы копируете музыку или документы, физика процесса одна и та же.
Сама прошивка имеет расширение .hex и представляет собой набор инструкций, в виде единиц и нулей, который понятен микроконтроллеру. Откуда взять прошивку? Ее можно скачать с сайтов по электронике, либо написать самому. Написать ее можно в специальных программах, которые называются средой разработки. Из наиболее известных мне AVR Studio, IAR, CodeVision, WinAVR… Нельзя сказать, что какая из этих сред лучше или хуже, каждому свое. Можно сказать, что различаются эти программы в основном удобством, языком программирования и ценой. В пределах данного сайта, рассматривается только CodeVision.
Со средой разобрались, теперь разберемся с процессом написания прошивки. В CodeVision изначально нужно создать проект. Его можно создать при помощи мастера кода или пустой. В любом случае, нужно выбрать тип используемого микроконтроллера и указать его частоту. При использовании мастера, вам будет предложено выбрать начальные настройки и сгенерировать исходный код с настройками. Далее появится окно, в котором можно редактировать этот код. Хотя вы можете написать свой исходный код, в блокноте и потом прицепить его к проекту в настройках.
Файл с исходным кодом является набором команд на языке программирования, задача CodeVision`а перевести эти команды в двоичный код, ваша задача написать этот исходный код. CodeVision понимает язык Си, файлы с исходным кодом имеют расширение «.c». Но у CodeVision есть некоторые конструкции, которые не используются в Си, за это его многие программисты не любят, а используемый язык называют Си подобным. Однако, это не мешает писать серьезные проекты. Множество примеров, генератор кода, большой набор библиотек дает большой плюс CodeVision`у. Единственный минус, то что он платный, хотя есть бесплатные версии с ограничением кода.
Исходный код должен содержать заголовок с используемым типом микроконтроллера и функцию main. Например, используется ATtiny13
#include <tiny13.h> void main(void) { }; |
До функции main можно подключить необходимые библиотеки, объявить глобальные переменные, константы, настройки. Библиотека это отдельный файл, обычно с расширением «.h», в котором уже есть заранее написанный код. В одних проектах этот код может быть нам нужен, а в других не нужен. Например, в одном проекте мы используем жк дислей, а в другом не используем. Подключить библиотеку для работы с жк дисплеем «alcd.h», можно так:
#include <mega8.h> #include <alcd.h> void main(void) { }; |
Переменные это участки памяти, в которые можно поместить некие значения. Например, сложили два числа, результат нужно, где то сохранить, чтобы использовать в дальнейшем. Сначала необходимо объявить переменную, т.е. выделить под нее память, например:
int i=0;
т.е. мы объявили переменную i и поместили в нее значение 0, int – тип переменной, или проще говоря, означает размер выделенной памяти. Каждый тип переменных может хранить только определенный диапазон значений. Например, int можно записать числа от -32768 до 32767. Если нужно использовать числа с дробной частью значит, переменную нужно объявлять как float, для символов используют тип char.
bit, _Bit 0 или 1 char от -128 до 127 unsigned char от 0 до 255 int от -32768 до 32767 unsigned int от 0 до 65535 long int от -2147483648 до 2147483647 unsigned long int от 0 до 4294967295 float от ±1.175e-38 до ±3.402e38 |
Внутри функции main уже выполняется основная программа. После выполнения функции программа остановится, поэтому делают бесконечный цикл while, который крутит одну и ту же программу постоянно.
void main(void) { while (1) { }; }; |
В любой части исходного кода можно написать комментарий, на работу программы он влиять никак не будет, но будет помогать сделать пометки к написанному коду. Закомментировать строку можно двумя слешами //после этого компилятор будет игнорировать всю строку, либо несколько строк /**/, например:
/*Основные математические операции:*/ int i=0; //объявляем переменную i и присваиваем ей значение 0 //Сложение: i = 2+2; //после выполнения данного выражения переменная i будет равна 4 //Вычитание: i = 2-2; //после выполнения данного выражения переменная i будет равна 0 //Умножение: i = 2*2; //после выполнения данного выражения переменная i будет равна 4 //Деление: i = 2/2; //после выполнения данного выражения переменная i будет равна 1 |
Зачастую в программе требуется выполнить переход от одного куска кода к другому, в зависимости от условий, для этого существует условные операции if(), например:
if(i>3) //если i больше 3, то присвоить i значение 0 { i=0; } /*если i меньше 3, то перейти к коду следующему после тела условия, т.е. после скобок {}*/ |
Также if можно использовать совместно с else – иначе
if(i<3) //если i меньше 3, то присвоить i значение 0 { i=0; } else { i=5; //иначе, т.е. если i больше 3, присвоить значение 5 } |
Также имеется операция сравнения «==» ее нельзя путать с «=» присвоить. Обратная операция не равно «!=», допустим
if(i==3)//если i равно 3, присвоить i значение 0 { i=0; } if(i!=5) //если i не равно 5, присвоить i значение 0 { i=0; } |
Перейдем к более сложным вещам – функциям. Допустим, у вас есть некий кусок кода, который повторяется несколько раз. Причем этот код довольно большой в размерах. Писать его каждый раз неудобно. Например, в программе, каким то образом изменяется переменная i, при нажатии на кнопку 0 и 3 порта D выполняется одинаковый код, который в зависимости от величины переменной i включает ножки порта B.
void main(void) { if(PIND.0==0) //проверяем нажата ли кнопка на PD0 { if(i==0) //если i==0 включить PB0 { PORTB.0=1; } if(i==5) // если i==5 включить PB1 { PORTB.1=1; } } … if(PIND.3==0)// выполняем тоже самое, при проверке кнопки PD3 { if(i==0) { PORTB.0=1; } if(i==5) { PORTB.1=1; } } } |
В общем, то код не очень большой, но он мог бы быть еще и больше во много раз, поэтому гораздо удобнее было бы создать свою функцию.
Например:
void i_check() { if(i==0) { PORTB.0=1; } if(i==5) { PORTB.1=1; } } |
void означает что функция ничего не возвращает, об этом чуть ниже i_check() – это название нашей функции, можете назвать как угодно, я назвал именно так – проверка i. Теперь мы можем переписать наш код:
void i_check() { if(i==0) { PORTB.0=1; } if(i==5) { PORTB.1=1; } } void main(void) { if(PIND.0==0) //проверяем нажата ли кнопка на PD0 { i_check(); } … if(PIND.3==0) { i_check(); } } |
Когда код будет доходить до строчки i_check(); то он перепрыгнет внутрь функции и выполнит код внутри. Согласитесь, код компактнее и нагляднее, т.е. функции помогают заменить одинаковый код, всего на одну строчку. Обратите внимание, что функция объявляется вне основного кода, т.е. до функции main. Можно сказать, да зачем мне это надо, но изучая уроки вам часто будут попадаться функции, например очистка жк экрана lcd_clear() — функция не принимает никаких параметров и ничего не возвращает, однако очищает экран. Иногда эта функция используется чуть ли не через строчку, так что экономия кода очевидна.
Намного интереснее выглядит применение функции, когда она принимает значения, например, есть переменная c и есть функция sum, которая принимает два значения типа int. Когда основная программа будет выполнять эту функцию, то в скобках уже указаны аргументы, таким образом «a» станет равной двум, а «b» станет равной 1. Функция выполнится и «с» станет равна 3.
int c=0; void sum(int a, int b) { c=a+b; } void main(void) { sum(2,1); } |
Одна из часто встречаемых подобных функций это перевод курсора у жк дисплея lcd_gotoxy(0,0); которая, кстати, тоже принимает аргументы – координаты по х и у.
Еще один вариант использования функции, когда она возвращает значение, теперь она уже не будет void, усовершенствуем предыдущий пример функции сложения двух чисел:
int c=0; int sum(int a, int b) { return a+b; } void main(void) { с=sum(2,1); } |
Результат будет такой же как и в прошлый раз c=3, однако обратите внимание, мы переменной «с» присваиваем значение функции, которая уже не void, а возвращает сумму двух чисел типа int. Таким образом мы не привязываемся к конкретной переменной «с», что добавляет гибкости в использовании функций. Простой пример подобной функции — чтение данных АЦП, функция возвращает измеренное значение result=read_adc();. На этом закончим с функциями.
Теперь перейдем к массивам. Массив это связанные переменные. Например, у вас есть таблица синуса с несколькими точками, не будете же вы создавать переменные int sinus1=0; int sinus2=1; и т.д. для этого используют массив. Например, создать массив из трех элементов можно так:
int sinus[3]={0,1,5};
в квадратных скобках указывается общее количество элементов массива. Присвоить переменной «с» значение третьего элемента можно таким образом:
с=sinus[2];
Обратите внимание, нумерация элементов массива начинается с нуля, т.е. «с» станет равна пяти. У данного массива элемента sinus[3] не существует!!!
Отдельному элементу можно присвоить значение так:
sinus[2]=10;
Возможно, вы уже успели заметить, что в CodeVision нет строковых переменных. Т.е. нельзя создать переменную string hello=”привет”; для этого придется создавать массив из отдельных символов.
char hello[6]={'п','р','и','в','е','т'}; |
Создали мы массив из букв, теперь их надо вывести на дисплей, функция вывода на дисплей одного символа, допустим нуля, выглядит так – lcd_putchar(0); т.е. чтобы вывести массив с приветствием придется по очереди выводить все буквы.
lcd_putchar(hello[0]); lcd_putchar(hello[1]); lcd_putchar(hello[2]); |
и т.д.
Получается довольно громоздко, тут на помощь приходят циклы.
Например цикл while
int i=0; while(i<6) { lcd_putchar(hello[i]); i++; } |
Выглядит намного компактнее, расшифровывается так: до тех пор пока i меньше шести, выводить каждый элемент массива по очереди. Т.е. код внутри будет выполняться, пока условие в скобках не станет неверным. Так же можно использовать «нестандартным» способом цикл while, например
while(PINB.0!=0) { } |
Пока кнопка не нажата ничего не делать – гонять пустой цикл.
Еще один вариант цикл for
int i; for(i=0;i<6;i++) { lcd_putchar(hello[i]); } |
Смысл точно такой же, как и у while только добавлено начальное условие i=0 и условие, выполняемое каждый цикл i++. Код внутри цикла максимально упрощен.
После того, как вы написали свою программу, исходный код компилируется, и если нет ошибок, то в папке с проектом вы получите заветную прошивку. Теперь можно прошивать микроконтроллер и наслаждаться работой устройства.
Не стоит сразу стараться использовать циклы, массивы и функции в своих прошивках. Ваша главная задача заставить прошивку работать, поэтому делайте так как вам проще не обращайте внимание на размер кода. Придет время когда захочется не просто писать рабочий код, а написать его красиво и компактно. Тогда можно будет углубиться в дебри языка Си. Желающим овладеть всем и сразу еще раз рекомендую книгу «Как программировать на Си», там много примеров и задач. Ставьте Visual Studio, создавайте консольное приложение win32 и там вволю тренируйтесь.
Помогите плиз надо написать библу на сдвиговый регистр, чтобы можно было простыми командами вывести на выходы любое состояние.
Например, пишем out_reg=0b01001011;
а получаем единицу на выходах Q0, Q1, Q3, Q6
про сдвиговые тут http://avr-start.ru/?p=2656
Хочу запрограммировать АТтини13. CodeVision сгенерировал такие строки
ADCSRA|=(1<<ADIF)
CLKPR=(0<<CLKPCE) | (1<<CLKPS3) | (0<<CLKPS2) | (0<<CLKPS1) | (0<<CLKPS0);
смог догадаться, что | используется как разделитель, А что значит |=, если бы было переменная<<число то это было бы циклическим сдвигом, а так? Где можно найти описание разновидности С, используемой в CodeVisio?
операция
ADCSRA |= (1 < < ADIF)
равносильнаADCSRA = ADCSRA | (1 < < ADIF)
, так что никаким разделителем там и не пахнет. Это операция логическое ИЛИ.1 < < ADIF
сдвинуть 1 в нужный бит, а когда вы объединяете то получается, что лог ИЛИ вы устанавливаете бит. По факту ADCSRA регистр, ADIF номер бита, в регистре ADCSRA, т.е. читается как установить бит ADIF.Спасибо! НО
Где можно найти описание разновидности С, используемой в CodeVisio?
Нет там никакой разновидности, просто закрытые библиотеки и конструкции типа PORTB.0, которыми вы можете не пользоваться. В остальном все как в обычном Си. Все что касается CAVR можно почитать в Codevision User Manual.
Огромная благодарность автору статьи- в одной странице разжевал то что не могли объяснить никакие книги млин из тех что я читал. И с применением массивов стало понятнее.
Очень хорошо и понятно объясняете,эта статья выше всяких похвал)
Здравствуй админ, извини, я снова с тупыми вопросами, просто спросить больше не у кого, а разобраться охота…
Объясни алгоритм добавления внешних библиотек.
Сделал шрифт для графического дисплея и не могу библиотеку с им подключить, постоянно CV ругается… Я файлик с библиотекой и в Code Navigator открыл и в настройках проекта добавил…
Ещё и конкретной инструкции не нашёл…
Спасибо!
добавляешь сишный файл в свойствах проекта и все. в хидере указываешь функции, если нужно наружу вытащить переменные то объявляешь через extern.
Здравствуйте! Скажите можно ли программно пропускать (игнорировать) часть кода. Например установкой джампера исключить из кода несколько строк, своего рода настройка.
можно, if условие
А если функция содержит строк 10-20, и скажем мне нужно исключить из них пару строк, то после if нужно опять переписать эту функцию, за исключением ненужных мне строчек, и по другому никак не получится? Просто тогда код сильно увеличится (при наличии нескольких джамперов), а памяти МК может не хватить. Я ещё дубовый в написании кодов, поэтому прошу понять и простить 😳 🙁
Помогите пожалуйста! Код:
void ST7735_drawChar(unsigned char x, unsigned char y, char ch, FontDef font, unsigned char color, unsigned char bgcolor)
{
unsigned char i, b, j;
int a = 0;
setAddrWindow(x, y, x+font.width-1, y+font.height-1);
for(i = 0; i < font.height; i++)
{
b = font.data[(ch — 32) * font.height + i];
for(j = 0; j < font.width; j++)
{
if((b <> 8, color & 0xFF };
while (data[a] != 0x00)
{
writedata(data[a]);
a++;
}
}
else
{
unsigned char data[2] = { bgcolor >> 8, bgcolor & 0xFF };
while (data[a] != 0x00)
{
writedata(data[a]);
a++;
}
}
}
}
}
Codevision ругается на строки unsigned char data[2] = { color >> 8, color & 0xFF }; с ошибкой invalid expression
в объявлении массива должны быть константы, у вас color явно не константа
Спасибо!
не компилируется код, была ли у вас такая проблема? Пишет что файлы не являются приложением Win32
Доброе время суток.
у меня программатор «STK500 v2» CodeVisionAVR его не определяет, в списке встроенных программаторов его нет. Подскажите пожалуйста, как можно привязать программатор к CodeVisionAVR?
Спасибо за Вашу помощь.
Добрый день, в мануале написано что stk500 поддерживается. Чтобы проверить все ли работает можете попробовать прошить через avr studio4.
Добрый вечер админ. Огромное спасибо Вам за Вши старания. Программатор работает с приложениями SinaProg и AVRDUDEPROG, без установки драйверов. Компьютер определяет как «HID — совместимое устройство USB — совместимое устройство», номер порта не указывается
Добры день админ. CVAVR увидел модуль преобразователь USB-TTL на CH340E(HW-728),
UART, определил номер порта, но пока не опробовал в работе
Добры день админ. CVAVR увидел модуль преобразователь USB-TTL на CH340E(HW-728), соединение через UART, определил номер порта, но пока не опробовал в работе
Добрый день!
Занимаюсь программирование ПЛК, очень привык к таким удобным вещам, как функциональные блоки и подпрограммы.
Подскажите, можно ли в CodeVisionAVR или AVR Studio 4 использовать что-то подобное?
Предстоит разработать большой проект, хотелось бы разбить его на части.
Например, есть 8 каналов для опроса датчиков, которые передают информацию в одинаковом виде, но на разные ноги МК. Можно ли как-то оптимизировать код, или нужно вручную каждый датчик прописывать?
напишите одну функцию, и в нее передавайте номер датчика
Добрый день. Подскажите, пожалуйста, как в attiny13 настроить таймер. Спаибо