Как начать работать с Microchip PIC32. Часть первая


_MG_9552-4

Как начать работать с Microchip PIC32?

1. Предсказуемые вопросы и ответы

Заранее отвечаю:

Вопрос: А почему бы школьникам и младшим студентам не использовать классический Ардуино?

Ответ: Микроконтроллер AVR в классическом Ардуино 8-битный, а Microchip PIC32 – 32-битный. PIC32 в несколько раз быстрее, чем 8-битный AVR, у PIC32 гораздо больше памяти, он позволяет писать более интересные программы. Кроме этого, опыт с основанным на ядре MIPS M4K микроконтроллером PIC32 может быть использован при работе с старшими ядрами MIPS, которые используются в планшетах и сетевых устройствах.

Вопрос: А почему бы школьникам и младшим студентам не использовать Ардуино-подобную систему разработки MPIDE вместо MPLAB X, используемой в инструкции?

Ответ: MPIDE – небольшая элегантная система для школьников, хоббистов, людей которые не любят читать документацию, а также инженеров, которым нужно смастерить что-то небольшое быстренько. MPLAB X – максимально гибкая профессиональная система, которая поддерживает весь спектр возможностей, предоставляемых PIC32. С моей точки зрения, разница в уровне сложности для начала работы с MPIDE и MPLAB X недостаточно велика, чтобы сначала учить MPIDE, а потом – MPLAB X. Если в конечном итоге человек собирается получить профессиональные навыки, лучше сразу начинать с MPLAB X. Если же целью является скажем научить программированию микроконтроллеров гуманитариев, то лучше использовать MPIDE и на нем оставаться.

Вопрос: А зачем вы работаете с устройствами ввода вывода прямо через регистры? Вот, я нашел в интернете библиотеку которая поддерживает SPI/UART/I2C и т.д.

Ответ: Одна из целей данного упражнения – научить работать именно на голом железе, без библиотек ввода-вывода. Это полезно не только для будущих писателей драйверов, но и для тех, кто хочет научиться делать системы, состоящие из хардвера и софтвера.

Вопрос: А почему вы используете плату Cerebot MX3cK с устройствами ввода-вывода Digilent Pmod, а не chipKit Uno32 c chipKit Basic I/O Shield, которую вы сами же рекомендуете для использования в школах и вузах?

Ответ: Они программируются очень похоже, только пины и устройства ввода-вывода другие. Пусть примеры для Uno32 сделают сами преподаватели для собственной практики, а их студенты не будут имет возможность у меня списывать. Uno32 лучше как универсальная платформа, чем Cerebot MX3cK, так как она совместима по пинам с Ардуино. Хотя для профессионального программиста встроенных систем ценность Ардуино-совместимости не очень велика, но для школьников, кружковцев и гуманитариев это предоставляет возможность использовать MPIDE и Arduino Shields. Иными словами, Uno32 подходит и для обучения профессионалов, и для обучения непрофессионалов, а Cerebot MX3cK для непрофессионалов менее удобен.

Вопрос: А как насчет ARM?
Ответ: Если вам хочется поста про ARM, то напишите его сами.

Вопрос: А как насчет Intel?

Ответ:Intel x86 плохо подходят для встроенных приложений из-за плохой метрики производительность / милливатт и много другого. Intel 8051 устарел и кроме этого плохо привязывается к курсу компьютерной архитектуры, в отличие от конвейерного MIPS M4K / Microchip PIC32. Кстати, один российский профессор сказал мне, что российское Министерство Образования рекомендует учить студентов программированию микроконтроллеров используя советский аналог микроконтроллера Intel 8048 (предшественника 8051) 😎

Вопрос: Это что, и есть FPGA / ПЛИС?

Ответ: Не, это не имеет никакого отношения к FPGA / ПЛИС-ам. Этот пост про программирование, встроенные софтвер. Использование FPGA / ПЛИС – это разработка хардвера, кроме случая, когда процессор имплементируется на FPGA. Примерчик калькулятора на FPGA у меня будет в отдельном посте. FPGA и микроконтроллеры это столь же разные материи, как конструирование автомобиля (хардвер) и его вождение (софтвер).

Вопрос: А можно ли с данной платой на Microchip PIC32 сделать не калькулятор, а игрушечного робота?

Ответ: Да, можно. Digilent продает набор для изготовления игрушечного робота, в который входит именно такая плата, сенсоры, моторчик, платформа из детского конструктора и колесики – см. http://digilentinc.com/Products/Detail.cfm?NavPath=2,403,1135&Prod=SRK-LINE.

2. Общая информация

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

_MG_9525

Документация к набору:

Информация про плату
http://digilentinc.com/Products/Detail.cfm?NavPath=2,396,984&Prod=CEREBOT-MX3CK

Документация к плате
http://digilentinc.com/Data/Products/CEREBOT-MX3CK/Cerebot_MX3cK_rm.pdf

Схематика к плате
http://digilentinc.com/Data/Products/CEREBOT-MX3CK/Cerebot%20MX3cK_sch.pdf

Информация про дисплейчик
http://digilentinc.com/Products/Detail.cfm?NavPath=2,401,473&Prod=PMOD-CLS

Документация к дисплейчику
http://digilentinc.com/Data/Products/PMOD-CLS/PmodCLS_rm_RevD-E.pdf

Схематика к дисплейчику
http://digilentinc.com/Data/Products/PMOD-CLS/PmodCLS_sch.pdf

Информация про клавиатурку
http://digilentinc.com/Products/Detail.cfm?NavPath=2,401,940&Prod=PMODKYPD

Документация к клавиатурке
http://digilentinc.com/Data/Products/PMODKYPD/PmodKYPD_rm.pdf

Схематика к клавиатурке
http://digilentinc.com/Data/Products/PMODKYPD/PmodKYPD_sch.pdf

Документация к микроконтроллеру – http://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en532434
Основной документ – PIC32 Family Reference Manual

Документация к системе разработки MPLAB X и система разработки как таковая – http://www.microchip.com/pagehandler/en-us/family/mplabx/

Код примерчика:

Код примерчика на Google code:
http://code.google.com/p/pic32-examples/source/browse/trunk/#trunk%2Fshowroom%2Fcalculator

Код примерчика на моем персональном сайте:
http://panchul.com/education/2013_02_03_calculator/sources/

Сразу разъясню, чтобы не было вопросов: я написал данный код с главной целью, чтобы его было легко читать начинающим программистам.
Как правило, легче чего читать код, когда его мало и не нужно смотреть в разные файлы.

Поэтому код использует короткие идентификаторы, буфера фиксированного размера, никакой C++ – ности, никакой реентерабильности, никаких штучек для портабильности на троичные машины и прочие извращения. Я НЕ пытаюсь показать, как структурировать гибкий код для большого многослойного индустриального проекта. Все максимально тупо, если торчащий из микроконтроллера провод называется G9, то и в коде он называется G9. Если вам это не нравится – вы можете написать свой пример и внести вашу версию в pic32-examples.

Итого, вот конструкция в собранном виде. Можно так:


_MG_9475-2

А можно – так:


_MG_9552-4

3. Работа с дисплеем с помощью протокола SPI – Serial Peripheral Interface

Дисплейчег имеет три способа к нему подсоединиться – UART, SPI и I2C. Я выбрал SPI – Serial Peripheral Interface. Подсоединение через SPI делается через пины слева сверху:

_MG_9488

Чтобы дать понять дисплейчигу, что мы подсоединяемся через SPI, нужно поставить вот эти джамперы вот таким способом:

_MG_9490-2

Сверху штырьки для SPI выглядят вот так:

_MG_9499

А с другой стороны платы дисплея – вот так:


_MG_9504-2

Чтобы соединить SPI порт дисплея с 2-м SPI портом микроконтроллера, соединительный кабель с шестью проводами нужно воткнуть в верхний ряд 12-дырочного коннектора E у лампочки. Можно втыкать дисплейчик напрямую, без кабеля.

_MG_9479-2

Также обратите внимание на табличку соединения сигналов из документации на плату:

Что все это физически означает? SPI – это просто способ передавать биты из пункта A в пункт B, одновременно с передачей других битов из пункта B в пункт A. Биты передаются по проводам SI и SO, причем момент передачи определяется изменением состояния синхросигнала CK. Другие провода на фотке означают V – питание +3.3 вольта, G – ground 0 вольт, SS – slave select, о котором мы поговорим чуть позже.

При этом устройство, которое генерирует синхросигнал, называется master (в русской терминологии – “ведущий”), а устройство, которое его принимает, называется slave (в русской терминологии – “ведомый”).

Вот так выглядит процесс передачи данных на уровне хардвера:

Передача и прием данных в PIC32 не требует от программиста работы на уровне сигналов. Все, что нужно для передачи – это поместить байт в регистр и ждать, пока он не будет передан. Ждать можно либо в цикле, либо используя прерывание. То же самое для приема данных, который осуществляется одновременно с передачей. В коде это выглядит вот так:

http://code.google.com/p/pic32-examples/source/browse/trunk/showroom/calculator/spi.c

char spi_put_get_char (char c)
{
    SPI2BUF = c;                   // send data to slave
    while (SPI2STATbits.SPIBUSY);  // wait until SPI transmission complete
    return SPI2BUF;
}


char spi_get_char (void)
{
    return spi_put_get_char (0);
}


void spi_put_char (char c)
{
    (void) spi_put_get_char (c);
}

Инициализируется SPI в самой простой конфигурации вот так:

http://code.google.com/p/pic32-examples/source/browse/trunk/showroom/calculator/spi.c

void spi_init (uint baud)
{
    char dummy;

    SPI2CONbits.ON      = 0;        // disable SPI to reset any previous state
    dummy               = SPI2BUF;  // clear receive buffer
    SPI2BRG             = PBCLK_FREQUENCY / 16 / baud - 1;
    SPI2CONbits.MSTEN   = 1;        // enable master mode
    SPI2CONbits.CKE     = 1;        // set clock-to-data timing
    SPI2CONbits.ON      = 1;        // turn SPI on
    . . . . . . . . . . . . 

Да, так насчет сигнала Slave Select. В PIC32 этот сигнал является вводом и используется только в специальном режиме, когда микроконтроллер является не master, а slave. В обычном режиме этот пин можно использовать как вывод из цифрового порта общего назначения, который называется G. А в дисплейчике с помощью специального джампера можно подключить данный пин к сигналу сброса (reset) дисплейчика. Что мы и сделаем. Если дисплею не делать reset определенное время, то он не будет нормально работать. Итого:

Джампер для сброса (reset):


_MG_9541

Почему это работает (обратите внимание на “Programing Jumper”):

Ниже код для сброса, который я поместил в функцию инициализации SPI. Длительность ожидания после сброса почему-то не указана в документации к дисплею, поэтому длительность пришлось устанавливать экспериментально. Обращаю внимание, что я жду (трачу время) используя цикл с nop-ами (пустыми операциями), а не при помощи аппаратного таймера, которых есть в M4K и PIC32 несколько. Таймер нам пригодится для прерываний во время работы с клавиатурой – лучше его не занимать для избежания конфликта.

http://code.google.com/p/pic32-examples/source/browse/trunk/showroom/calculator/spi.c

void spi_init (uint baud)
{
    . . . . . . . . . . . . 

    // Signal G9 is on the same pin as SPI2SS (Slave Select) signal.
    // Since slave select is not used in this configuration
    // the pin is used as a reset signal for the external SPI slave.

    TRISGbits.TRISG9    = 0;
    PORTGbits.RG9       = 0;
    delay_for_1000_nops_x (1000);
    PORTGbits.RG9       = 1;
    delay_for_1000_nops_x (1000);
}

4. Следущий уровень работы с дисплеем – escape-последовательности

Данный дисплей сам имеет внутри собственный микроконтроллер (не PIC32, а 8-битный AVR), который интерпретирует поступающие снаружи байты как текст и команды (escape-последовательности). В данных командах вы разберетесь сами по документации и моему коду, который использует escape-последовательности только для позиционирования курсора и приведен ниже. Обращаю внимание, что я установил скорость передачи информации в 100000 байтов в секунду. Сергей Вакуленко говорил, что SPI может выдержать миллионы, но с данным дисплейчиком скорости больше 100000 не получается – наверное его внутренний микроконтроллер не может обрабатывать быстро.

Да, я знаю, что “нормальный программист изолировал бы “spi_put_str (“\033[0;0H”); // set cursor position to row 0 column 0″ – в отдельную функцию”. Как я уже сказал – цель данного кода чтобы его было меньше, и следовательно, чтобы его можно было бы быстрее прочитать без взгляда скользящего по нескольким функциям. Нет, я не буду заворачивать эту функциональность в C++ класс, хотя это хорошая идея для создания абстрактной иерархии классов, работающих с разными дисплеями. Но не в этом примере.

http://code.google.com/p/pic32-examples/source/browse/trunk/showroom/calculator/display.c

static char buf [16];
static int col;

void display_init (void)
{
    spi_init (100000);  // baud rate

    memset (buf, ' ', sizeof (buf));
    col = 0;
}

static void display_scroll ()
{
    int i;

    spi_put_str ("\033[0;0H");  // set cursor position to row 0 column 0

    for (i = 0; i < sizeof (buf); i++)
        spi_put_char (buf [i]);

    spi_put_str ("\033[1;0H");  // set cursor position to row 1 column 0

    for (i = 0; i < sizeof (buf); i++)
        spi_put_char (' ');

    spi_put_str ("\033[1;0H");  // set cursor position to row 1 column 0

    memset (buf, ' ', sizeof (buf));
    col = 0;
}

void display_char (char c)
{
    if (c == '\n')
    {
        display_scroll ();
        return;
    }

    spi_put_char (c);
    buf [col ++] = c;

    if (col == sizeof (buf))
        display_scroll ();
}

void display_str (char *s)
{
    while (*s != '\0')
        display_char (*s++);
}

5. Работа с клавиатурой - опрос, буфер и прерывания

Теперь перейдем к работе с клавиатурой. Данное устройство не использует никаких протоколов типа USB или PS/2. Оно использует восемь проводов ввода-вывода, провод питания, кнопочки и резисторы. Программист работает с этими проводами непосредственно, как с битами в регистре E. Концептуально опрос состояния клавиатуры выглядит очень просто. Четыре провода вывода соответствуют четырем вертикальным колонкам - col1, col2, col3, col4, а четыре провода ввода соответствуют четырем горизонтальным рядам - row1, row2, row3, row4. Если подать на col1 ноль, а на col2, col3, col4 - единицы, то на вводах row1-4 появятся состояния нажатости клавиш в колонке 1. Действительно (см. схему), если клавиша "1" нажата, то на row1 появится 0, который пришел от col1, а если не нажата - то прийдет единичка от VCC через pull-up резистор R1 (если вы очень чистый программист и никогда не сталкивались с pull-up резисторами, то я расскажу об этом в отдельном посте). Короче, вот схема:

А вот код, который опрашивает состояние клавиатуры, проверяя что нажато и сравнивая это с состоянием во время предыдущей проверки. PORTE - это регистр, непосредственно подключенный к проводам, идущим к клавиатурке.

http://code.google.com/p/pic32-examples/source/browse/trunk/showroom/calculator/keypad.c

static void keypad_poll ()
{
    int row, col;
    int in;

    for (col = 0; col < n_cols; col ++)
    {
        PORTE = ~ (8 >> ((col + 1) & 3));
        in = PORTE >> 4;

        for (row = 0; row < n_rows; row ++)
        {
            bool on = ! (in & 8);
            in <<= 1;

            if (on && ! matrix [row][col])
                keypad_put (translation [row][col]);

            matrix [row][col] = on;
        }
    }
}

Клавиатура вставляется в 12-дырочный разъем JA или напрямую, или через кабель - так, что и клавиатура и плата "смотрят" в одну сторону. Разъем находится около кнопки сброса POC32 (reset):

_MG_9508

_MG_9507

Также вот схематика пинов из документации по клавиатурке:

И табличка из документации на микроконтроллерную плату:

Опрос клавиатуры удобно посадить на прерывание по таймеру - например опрашивать 50 раз в секунду. Это предотвратит дребезг (bouncing), когда при нажатии на клавишу контакт дергается вверх-внизы, вызывая у программы галлюцинации, что на кнопку нажали много раз. Кроме этого, работа на таймере высвобождает микроконтроллер от постоянного опроса ввода-вывода - вместо этого микроконтроллер может заниматься какими-нибудь полезными вычислениями. Вот как устанавливается прерывание по таймеру:

http://code.google.com/p/pic32-examples/source/browse/trunk/showroom/calculator/keypad.c

//
//  The Timer 1 interrupt is Vector 4, using enable bit IECO<4>
//  and flag bit IFSO<4>, priority IPC1<4:2>, subpriority IPC1<1:0>

void __attribute__ ((interrupt (IPL7))) __attribute__ ((vector (4))) keypad_timer (void)
{
    keypad_poll ();
    IFS0bits.T1IF = 0;
}

void keypad_init (bool use_interrupts)
{
    . . . . . . . . . . . .

    T1CONbits.ON     = 0;      // turn timer off
    TMR1             = 0;      // reset timer to 0

    T1CONbits.TCKPS  = 3;      // 1:256 prescale
    PR1              = PBCLK_FREQUENCY / 256 / 50;  // 1/50th of a second

    INTCONbits.MVEC  = 1;      // enable multi-vector mode
    IPC1bits.T1IP    = 7;      // interrupt priority
    IPC1bits.T1IS    = 3;      // interrupt subpriority
    IFS0bits.T1IF    = 0;      // clear the Timer 1 interrupt flag
    IEC0bits.T1IE    = 1;      // enable the Timer 1 interrupt

    asm volatile ("ei");       // enable interrupts

    T1CONbits.ON     = 1;      // turn timer on
}

В моей имплементации работы с клавиатурой есть также промежуточный буфер введенных цифр, который может переполняться - но это все очевидно из кода, и я не буду разжевывать.

6. Как вставить программатор / отладочный модуль

Программатор / отладочный модуль - это устройство, которое предназначено связывать микроконтроллерную плату с системой разработки на персональном компьютере. Программатор поддерживает отладочный протокол, который позволяет записывать программу и данные с PC в память микроконтроллера, устанавливать точки останова во время отладки и т.д.

chipKIT PGM Programmer/Debugger - это новое устройство, и в его первой партии, которая разошлась осенью, был брак - из-за двух неправильных резисторов устройство работало как программатор, но не работало как отладчик. Сейчас Digilent бракованные программаторы позаменяло. Также можно использовать старые надежные отладчики PICkit3:

http://www.digilentinc.com/Products/Detail.cfm?NavPath=2,739,974&Prod=PG164130

Но я все-таки использую chipKIT PGM Programmer/Debugger, исправленную партию без брака. Вот как он выглядит:


_MG_9515

Он вставляется в такую группу дырочек, которые специально сделаны неровно, чтобы программатор из них не выпадал:

_MG_9513

Вот как все выглядит в вставленном состоянии:

_MG_9479-2

7. Калькулятор в работе

А теперь моя программка-калькулятор в работе. Она принимает целые числа, четыре операции арифметики: A это +, B это -, С это *, D это /; далее клавиша E - это скобка (левая или правая, в зависимости от предудущего ввода) и клавиша F - это "равно". Парсер / эвалюатор выражений использует простую рекурсию сверху-вниз. Вообще, я предпочитаю более надежный для общего случая bottom-up operator precedence parser, или вообще обобщенный LR(k) парсер, но всевозможные парсеры и эвалюаторы выражений пусть пишут студенты 1-2 курса в качестве упражнений.

Код калькулятора:

http://code.google.com/p/pic32-examples/source/browse/trunk/showroom/calculator/calculator.c

Головная программа:

http://code.google.com/p/pic32-examples/source/browse/trunk/showroom/calculator/main.c

void main (void)
{
    int i;

    running_fast ();
    display_init ();
    keypad_init  (true);  // use_interrupts

    display_str ("Calculator");

    for (;;)
        display_str (calculator (keypad_get ()));
}

Калькулятор в работе:

Заставка:

_MG_9481

Без скобок:

_MG_9484

Со скобками:

_MG_9485

Синтаксическая ошибка:

_MG_9487

Переполнение:

_MG_9486

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


_MG_9531

8. Другие примеры на основе плат с Microchip PIC32

Другое изделие на основе этой же самой платы - игрушечный робот на двигающейся платформе:

http://digilentinc.com/Products/Detail.cfm?NavPath=2,403,1135&Prod=SRK-LINE.

Продолжение
http://panchul.com/2013/02/27/microchip_pic32_2/

3 thoughts on “Как начать работать с Microchip PIC32. Часть первая

  1. Интересно, почему в России и Украине для встраиваемых систем становятся популярны преимущественно микроконтроллеры и процессоры ARM архитектуры, на которые разработчики плавно но уверенно переходят с 8-битных AVR, ну и для тех кто постарше, PIC? Почему другие компании *боятся* MIPS ядра? Например TI и ADi в своих микроконтроллерах упорно используют ARM.

    Reply

    Yuri Panchul reply on May 9th, 2013 3:58 pm:

    Костя: Другие компании не “боятся” ядер MIPS. Вы можете посмотреть на длинный список компаний, которые являются лицензиатами ядер MIPS вот здесь – http://www.mips.com/customers/licensees/index.dot

    Как вы видите, в них входят Broadcom, Cisco, Lantiq, PMC Sierra, Ralink, SiS, Zoran и многие другие.

    Microchip Technology использует в микроконтроллерах PIC32 ядра MIPS M4K/M14K/microAptiv потому, что у них есть технические преимущества по сравнению с соответствующими им ядрами ARM Cortex M0/M3/M4. Для независимого сравнения позиции MIPS против ARM в микроконтроллерах текущего поколения см. статью в Microprocessor Report http://panchul.com/dropbox/2012_11_19/234601_micromips.pdf

    А также мою презентацию

    http://microchip.com.ru/Support/MASTERs2012/P3212.pdf

    А для данных о следущем поколении микроконтроллеров PIC32 следите за объявлениями Microchip. Данные о новом PIC32MZ начали просачиваться в документацию и прессу, и вы можете их нагуглить.

    Reply

Leave a Reply