Свежие обсуждения
Микроконтроллеры

Ликбез по С для микроконтроллеров PIC

1 8 64

AHTOXA. Что-то я не понимаю. Вы предлагаете не пользоваться графической оболочкой и всеми опциями программы, а работать из командной строки? Только компилировать? А текст писать в блокноте?
Да нет у меня проблем с компиляцией готовой программы и при запуске из графической среды. По-моему, сама по себе компиляция, это чрезвычайно простая процедура. Я делал ее и из под PICC и из под MPLAB.

Спасибо, конечно, но как-то для меня странновато малость. Выходит, нет какой-то проторенной дорожки к изучению С для МК для начинающего? Необходимо сначала "засесть" за фундаментальное изучение С применительно к ПК, с примерами и задачами, ну как во всех книгах и руководствах по С. Когда приобретешь достаточный опыт и знания, можно уже пересаживаться за МК, добавляя к полученным знаниям ту специфику, которая здесь требуется. Так? Никак нельзя сразу изучать применительно для МК? Я понял так, что нельзя. Жаль что нет каких-то книг и руководств с нуля. Просто сейчас продираюсь через все это методом "научного тыка", а посему трачу чрезвычайно много свободного времени, которого у меня и так нет.

 

2 Zandy
Давайте с самого начала и по порядку. В этой ветке люди хотят научиться писать программы для МК ПИК на языке Си. Что для этого нужно? Компилятор, среда разработки, желание. Среда разработки у нас есть, всем привычный МПЛАБ. С компилятором тоже определились, CCS PICC. Лично я считаю что лучше было бы начать с компилятора от хайтек и книги Предко "Устройства управления роботами". Но раз уж выбрали ЦЦС, значит будем практиковаться в нем. Далее качаем с сайта CCS вязалку компилера с МПЛАБОМ вяжем их. На среде разработки предложенной ЦЦС лучше НЕ заморачиваться, и такой вешью как PIC WIZARD по началу не пользоваться. Нужно научиться создавать проекты с чистого листа.
Итак, создаем в МПЛАБ новый проект, выбираем компилятор CCS C Compiler и нужный нам девайс. Создаем чистый исходник без всяких примеров и пишем следующее:
#include <16f628.h>

void main()
{

}
Компилируем. Думаю ошибок не будет=)
main() в Си является главной функцией, без нее программа существовать не может. Главная функция управляет всеми остальными. Что дальше? дальше нам нужно хотябы инициализировать конфигурационный регистр, т.е. сконфигурировать МК так как нам надо. Для этого в ЦЦС Си используется директива #FUSES. Чтобы инициализировать конфигурационный регистр, нужно ВНЕ тела функции Мэин() написать директиву #FUSES и список параметров через запятую. Например:
#FUSES INTRC,NOLVP,NOWDT,PUT,MCLR, NOCPD,NOPROTECT
Теперь наш МК сконфигурирован. INTRC - значит что мы выбрали внутренний тактовый генератор;NOLVP - низковольтовое программирование отключено;NOWDT - вотчдог отключен и т.д. Все возможные параметры директивы #FUSES можно посмотреть в среде разработки ЦЦС. Запустите среду разработки,клацните меню view->Valid Fuses, затем выбираем модель МК и смотрим. Все доступные параметры перечислены в столбик с комментариями.
Что дальше? давайте помигаем светодиодами. Что для этого нужно? записать единицы в порт, выполнить задержку и затем записать нули в порт. Для записи значения в порт используют выражение OUTPUT_A,(value) где А - имя порта.Чтобы использовать функцию задержки нужно написать директиву #use delay (clock=speed), где speed частота тактирования в герцах. Т.е. чтобы функции delay_ms() delay_us() работали корректно на частоте 4 МГц, нужно написать #use delay (clock=4000000). Что должлно получиться в итоге:
#include <16f628.h>
#FUSES INTRC,NOLVP,NOWDT,PUT,MCLR, NOCPD,NOPROTECT
#use delay (clock=4000000)
void main()
{
set_tris_b(0x00); //PORTB на вывод
while (1)// бесконечный цикл
{
output_b(0x00); //светодиоды не горят
delay_ms(1000);// задержка на секунду
output_b(0xff);//горят
delay_ms(1000);
}
}

 

Zandy: AHTOXA. Что-то я не понимаю. Вы предлагаете не пользоваться графической оболочкой и всеми опциями программы, а работать из командной строки?

Ну да. Вы же сами жаловались, что запутались Я описал свой подход, Вы попросили поподробнее.

Не подумайте, что я пишу тексты в блокноте а команды набиваю каждый раз от руки
Пишу я в удобном редакторе (med, потомок знаменитого multiedit-а) - подсветка синтаксиса, переход по дереву вызовов, макросы, куча удобств. А компилятор, программатор и проч - вызываются из меню.

Таким образом, я получаю универсальную среду для практически любого микроконтроллера.

Мне кажется (но я не навязываю своего мнения), что лучше уж разобраться в этой методе, чем каждый раз осваивать новые (и зачастую очень корявые) среды программирования...

Zandy: Выходит, нет какой-то проторенной дорожки к изучению С для МК для начинающего?

Ну почему же нет? Берёте пример, разбираете. Вам совершенно не надо поначалу углубляться в дебри Си. Достаточно знать несколько простых вещей:

- функция main();
- как написать другую функцию;
- как вызвать другую функцию;
- арифметические операции, приоритеты, скобки;
- типы данных (для начала хватит unsigned char - байт и unsigned int - это слово);
- битовые операции (как проверить, установлен или сброшен бит, как установить или сбросить бит);
- как объявить обработчик прерывания;
- как в обработчике узнать источник прерывания;
- как объявить массив переменных, как писать/читать его элементы

Ну и, пожалуй, всё
С этим набором Вы легко повторите проект из ликбеза.

Если какие-то из этих пунктов неясны, напишите какие, я постараюсь объяснить.

Zandy: а посему трачу чрезвычайно много свободного времени, которого у меня и так нет.

Ну Вы-то хоть на себя его тратите

 

Maksim_86, большое спасибо за подробное конкретное объяснение. Маленькие вопросики по сообщениям.
Значит все-таки директивы препроцессора надо брать из конкретного компилятора, в данном случае CCS PICC? А как же быть тогда с универсальной средой, про которую говорит AHTOXA? Или мнемоника всех директив одинакова для всех компиляторов и МК? Такого же просто не может быть! Не понимаю.

Далее. Вот вы пишете #FUSES INTRC,NOLVP,NOWDT,PUT,MCLR, NOCPD,NOPROTECT в одну строчку. А если воспользоваться WIZARD, то в результате получаем каждую директиву отдельно, ну типа так:

#FUSES NOWDT //No Watch Dog Timer
#FUSES INTRC //Internal RC Osc
#FUSES NOPUT //No Power Up Timer
#FUSES NOPROTECT //Code not protected from reading
#FUSES NOBROWNOUT //No brownout reset
#FUSES NOMCLR //Master Clear pin used for I/O
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOCPD

Это тоже самое?

Далее. Инициализация. У вас это одна строчка:

set_tris_b(0x00); //PORTB на вывод

Вот тут 2 вопроса. Откуда взята мнемоника? Имеется ввиду слово set_tris_b? Это что переменная? Какая переменная? Почему не указываем тип? Где оператор присваивания? Если мы ее в тексте программы не задаем, значит она где-то уже определена? Где? Или это функция, судя по круглым скобкам? Как бы с этим разобраться. Второй вопрос. Когда я пишу программу на асме, я инициализирую кучу регистров, которые использую в программе и которые надо инициализировать. Причем, как специальные, так и общие. А тут как с этим? Надо ли инициализировать специальные регистры, ну к примеру, заведующие прерываниями, таймерами, включением - отключением компараторов и т. д.
Просто, когда я запускаю пресловутый WIZARD, он мне пишет, например, такое:

setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
setup_timer_1(T1_DISABLED);

Как видите, про регистры тут ни слова, а используется какая-то особенная специфическая мнемоника. К чему бы это? Откуда это? Как это? Или все это можно по-разному делать?
Мне бы "нащупать" хоть кусочек твердой почвы, хоть под одной ногой , а дальше бы я начал сам вникать.

 

Ув Zandy, влезу в обсуждение по одному из вопросов. В AVR-овском С есть, например, функция cli(), которая запрещает прерывания. Если пошариться в директории INCLUDE системы WinAVR, то в одном из файлов можно будет обнаружить вот такую любопытную строчку:

# define cli() __asm__ __volatile__ ("cli" :

Т.е. при вызове этой функции компилятор просто заменит ее ассемблерной командой cli Не исключено, что если пошариться по директории с файлами *.h (с помощью поиска, естественно), в одном из них можно будет найти нечто подобное и для Ваших set_tris_b, set_timer и прочих функций. Почти наверняка такая функция окажется ассемблерной вставкой, пишущей что-то в соответствующий порт. Тут на меня уже наехали, что я новичкам лишние знания сообщаю, но если у Вас возникнут проблемы с пониманием той или иной функции, это - один из способов разобраться с такой проблемой. Рекомендую

 

По поводу универсальной среды:
Синтаксис языка и смысл выражений не зависит от архитектуры процессора. Выражение
if (a==5)
b++;
будет всегда будет значить " если а равно 5, то инкрементировать b, иначе пропустить" независимо от того кто производитель компилятора и для какого процессора этот компилятор. Вот в чем универсальность. Небольшой примерчик:
Под новый год я сел разбираться с модулем USART. Поставил себе задачу нарисовать на экране компьютера зведочками елку( т.е. символом " * " ) Вот только запутался с тем, где мне поставить возврат каретки и сколько пробелов нужно отлистать. Проверять это каждый раз прошивая МК было довольно напряжно, поскольку КОМ шнурок только 1. Т.е. мне нужно было прошить, выключить комп, подсоединить шнурок к отладочной плате. Если я где-то ошибся и мою елочку нарисовало криво, выключаю комп подсоединяю шнурок обратно к программеру..... Вобщем последовательность действий понятна=) Вместо этого я запустил Microsoft Visual Studio, создал консольное приложение, забил массив звездочками, пробелами, символами возврата каретки и перевода строки. Вывел это дело на экран. Естественно правильно нарисовал не с 1 раза=) Сначала я елочку правильно вывел на экран в VC++, и только потом перенес кусок этого кода в МК. Этот же код можно и в АВР загнать, лишь с поправкой на другие имена регистров и их адреса.
Мораль сей басни такова: язык Си одинаков для всех процессоров. Теперь понятна в чем универсальность?
А вот #директивы препроцессору могут быть различными. В общем случае, директива препроцессору - это указание компилятору сделать что-то перед компиляцией. Есть стандартные директивы, а есть специфические для определенного компилятора. И что мне ужасно не нравится в ЦЦС, специфических директив препроцессору ПРОСТО НЕМЕРЯНО. И не все они прозрачны.
Какой код сгенерит директива #use RS232(params)? Не имею ни малейшего понятия. Я не совсем уверен что этот код будет оптимальным по размеру и быстродействию. Уж лучше я напишу функцию инициализации модуля USART чем воспользуюсь этой директивой.Не то что #use RS232 не стоит использовать вообще, но если размер и быстродействие в разрабатываемом Вами приложении являются очень критичными параметрами, лучше этой директивой побрезговать. Директива #FUSES в хайтековском компиляторе отсутствует. Там слово конфигурации задается функцией __CONFIG(ConfigWord); Но просматривая исходники под АВР такую директиву встречал. Т.е. данная директива есть не во всех компилерах
Что касается функций типа set_tris_x(), setup_timer0 и т.д.
Где и как они реализованы не имею ни малейшего понятия. Посмотрев заголовочный файл под девайс, реализации этих функций я там не нашел. Как они реализованы могу только догадываться, НО!, все они работают НАПРЯМУЮ С РЕГИСТРАМИ. А мнемоника этих функций выдрана из хелпа.
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1); - эта функция должна инициализировать регистр OPTION. Как именно это происходит я без понятия.
Тело функции get_timer0() будет выглядеть так: return TMR0 или movf TMR0,w.
Тело функции set_tris_a(value) будет выглядеть так: movlw value, movwf TRISA, а на Си так TRISA=value.
Естественно под именами TRISA, TMR0 и т.д. подразумеваются адреса по которым находятся данные регистры. Как по мне, выражение TRISA=value выглядит намного лучше чем set_tris_a(value), и писать меньше=)

 

Они не обязательно описаны именно в этом заголовочном файле. Я не случайно обмолвился про режим поиска. Попробуйте поискать во всех *.h строчку "set_tris_".

 

Zandy: А что же делать в PIC C? В нем то проект надо создавать?
Нет, не надо. После того, как проделаете эти шаги, также как и для асма: Debugger->SelectTools->MPLabSim. -появится привыная панелька с Run, паузой, пошаговым проходом. Далее Project->Compile(или F10) -запускается компилятор (появляется окошко CCS) и выдаётся отчёт, например
Clean: "куча разных файлов"
Clean: Done.
Executing: "C:\Program Files\PICC\Ccsc.exe" "BitTest.c" +FM +DF +LN +T -A +M +Z +Y=9 +EA
>>> Warning 203 "C:\CCS_examples\BitTest\BitTest.c" Line 23(1,1): Condition always TRUE
Memory usage: ROM=4% RAM=3% - 5%
0 Errors, 1 Warnings.
Loaded C:\CCS_examples\BitTest\BitTest.cof.
BUILD SUCCEEDED: Wed Jan 24 10:18:34 2007

Всё. Ставите точки останова, запускаете Run и отлаживаете свою прогу в привычном режиме.
Насчёт протеуса- это дело вкуса, я им вообще не пользуюсь, хватает связки компилятор-MPLab.

Zandy: Откуда взята мнемоника? Имеется ввиду слово set_tris_b
Эти функции описаны у Шпака в конце книги в приложении Д.

 

Max_Pinchuk: а в "хидере" .h для используемого МК указано какой физический адрес в нутри МК
В CCS PICC в стандартном хидере адреса регистров не указаны. Не вполне понятно, зачем они сделали всё через функции? Впрочем, никто не запрещает самому прописать адреса регистров и сделать свой собственный дополнительный хидер.
Стандартные хидеры для 628 я уже распечатал и держу перед глазами. Рекомендую всем новичкам сделать то же самое. Там все функции прописаны. Только вот целесообразно ли пользоваться ими? Или всё-таки сделать свой дополнительный хидер, прописать в нём все адреса регистров, и пользоваться им? Тогда просто достаточно будет указывать:
TRISB = 0xF0;
CMCON = 0x07;
и т.п. Но есть ещё хитрость с переключением направления выводов портов. Есть такие функции #use standart_io, #use fast_io. Почитать о них можете на стр.143 книги Шпака.
Я постепенно вникаю в Си, только времени мало на это. Работа, дела...

 

Самому писать в ЦЦС заголовки не обязательно. Запускаем ЦЦС
меню view->Special registers->registers. Там выбираем нужный нам девайс и нажимаем Make Include File.Byte prefix & Bit prefix лучше убрать, ато все биты и байты будут с приставкой типа MCU_TRISA. И вы бираем путь, где будет наш хеадер создан. Вуаля, заголовочный файл готов.