Печать

Самый лучший способ изучить новый язык программирования - Это сразу начать писать на нём программы.

Брайн Керниган и Деннис Ритчи

Традиционно, первой программой, с которой начинают изучение нового языка бывает программа "Hello World".

Первые шаги

Чтобы начать писать программу на языке Си для микроконтроллера RP2040 распаянного на плате Raspberry Pi Pico в Интегрированной Среде Разработки (IDE) Visual Studio Code (VS Code) выберите на левой панели кнопку Raspberry Pi Pico Project.

В панели левого меню  Raspberry Pi Pico Project : Quick Access выберите пункт меню New C/C++ Project. Откроется окно изображённое на рисунке 1.

Рис. 1. Visual Studio Code. Окно New C/C++ Project.

В окне изображённом на Рис. 1 в поле Name введите имя нового проекта. У нас проект будет называться "HelloWorld01".

В выпадающем списке Board Type выберите pico - название платы с микроконтроллером RP2040 фирмы Raspberry Pi.

В поле выбора папки (Location) выберите в своём рабочем каталоге папку в которой будут сохраняться все ваши проекты создаваемые в среде VS Code.

Поставьте галочку в check box Console over USB (disables other USB use). Это необходимо, так как мы собираемся писать программу с выводом текста в консоль компьютера и вывод этот будет происходить по USB интерфейсу.

Теперь, в окне New C/C++ Project можно нажать кнопку Create. Кнопку Create вы найдёте в нижней части окна New C/C++ Project, возможно придётся воспользоваться полоской прокрутки окна.

В панели левого меню откроется Проводник. См. Рис. 2.

Рис. 2. Visual Studio Code. Проводник и программа на Си.

В проводнике выберите программу на языке Си. У нас имя файла с программой совпадает с названием проекта, а расширение файлов с программами на Си состоит из одной буквы "c" после точки. Так что, это файл HelloWorld.c

В рабочем окне программы VS Code откроется файл HelloWorld.c с программой на языке Си для редактирования. Как вы видите, этот файл не пустой, это заготовка программы с возможностью использования вывода текста в консоль компьютера и вывод этот будет происходить по USB интерфейсу. Программа HelloWorld01.c полностью законченный вариант программы для вывода текста  "Hello, World!" в терминал компьютера, если компьютер и плата с микроконтроллером соединена по USB интерфейсу. В этой программе все конструкции языка Си используются корректно, в соответствии с синтаксисом языка, и несмотря на то, что это заготовка для написания своей программы, это полноценная работоспособная программа - пример.

Программа HelloWorld01.c, перед запуском должна быть скомпилирована и загружена в микроконтроллер RP2040 установленный на плате Raspberry Pi Pico. В правом нижнем углу в программе VS Code расположены кнопки Compile и Run.

Выбор кнопки Compile запускает процедуру компиляции программы. В процессе компиляции текст программы на языке C/C++ (файл с расширением c) компилируется в результате чего создаётся объектный файл с расширением obj. Далее, происходит линковка в результате чего создаётся файл с расширением elf. Elf файл конвертируется в файл формата uf2. Файл формата uf2 содержит вашу программу для микроконтроллера RP2040 формате двоичного кода, который микроконтроллер может читать, понимать и выполнять.

Компиляция программы происходит в папке build вашего проекта. Структура файлов в этой папке показана на следующем листинге:

build/
├── CMakeFiles/              # Служебные файлы CMake
│   ├── 3.25.1/              # Версия CMake
│   ├── CMakeTmp/
│   ├── YourProject.dir/      # Файлы вашего проекта
│   │   ├── src/
│   │   │   └── main.c.obj   # Объектный файл
│   │   ├── build.make
│   │   ├── flags.make
│   │   └── ...
├── pico-sdk/                # Локальная копия SDK
│   └── ...
├── generated/               # Сгенерированные файлы
│   └── ...
├── CMakeCache.txt           # Кэш настроек CMake
├── Makefile                 # Главный make-файл
├── YourProject.elf          # Исполняемый файл (ELF)
├── YourProject.uf2          # Прошивка для загрузки
├── YourProject.bin          # Бинарный образ
├── YourProject.hex          # HEX-образ
├── YourProject.dis          # Дизассемблированный код
└── ...

Для программиста, в папке build может представлять интерес файл с расширением dis. В этом файле находится дизассемблированная программа, созданная из двоичного кода и дополненная мнемониками на языке Assembler для вашего микроконтроллера.

Загрузка программы в микроконтроллер и запуск программы на выполнение.

Если все действия по подключению вы выполнили правильно, компьютер обнаружит подключение нового USB устройства (Flash Card RPI-RP2).

После компиляции программы в VS Code нажмите кнопку Run. Программа будет загружена в микроконтроллер и сразу же запустится. USB Flash карта отключится автоматически.

Мы запустили на выполнение программу "Hello World". " с выводом текста в консоль компьютера и вывод этот будет происходить по USB интерфейсу. Наш микроконтроллер уже подключен к компьютеру USB кабелем, теперь необходимо увидеть вывод текста "Hello World!" на компьютере и убедиться, что программа в микроконтроллере работает.

В VS Code В правом нижнем углу во время компиляции и загрузки программы в микроконтроллер открылся терминал, через который мы могли наблюдать сообщения от процесса компиляции и загрузки. Теперь, выберите в меню этого окна с сообщениями пункт SERIAL MONITOR. См. рис. 3. Если Serial Monitor (Монитор последовательного интерфейса) настроен правильно, вы должны увидеть в окне монитора строки "Hello World" появляющиеся каждую секунду. 

Рис. 3. Visual Studio Code.

Как работает программа

Мы воспользовались примером программы "Hello World", см. рис. 2 и лист. 2, давайте разберёмся как она работает.

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

#include <stdio.h>
#include "pico/stdlib.h"


int main()
{
    stdio_init_all();

    while (true) {
        printf("Hello, world!\n");
        sleep_ms(1000);
    }
}

Лист. 2. Программа "Hello World"

В нашем примере главная функция имеет имя main. Обычно функциям можно давать любые имена по своему усмотрению, но main - особый случай, поскольку программа начинает выполняться именно с этой функции. Это означает, что в любой программе на языке Си обязательно должна присутствовать функция main.

Обычно из функции main для выполнения разных операций вызываются другие функции, некоторые из них программист пишет сам, а другие содержатся в стандартных библиотеках. В функции main первой вызывается функция stdio_init_all, это функция из библиотеки pico/stdlib.h.

В строке программы #include "pico/stdlib.h" мы указываем компилятору включить в программу информацию о библиотеке pico/stdlib.h.

Один из способов передачи данных между функциями состоит в том, что функция при обращении к другой функции передает ей список значений, называемых аргументами (параметрами). Этот список заключают в круглые скобки и помещают после имени функции. В нашем примере main определена как функция без аргументов, на что указывает пустой список (). Операторы функции заключают в фигурные скобки. Операторы внутри фигурных скобок называют телом функции. Функция main в примере листинг 1 содержит 2 функции, которые будут выполнены по очереди сверху вниз по программе.

Первая функция в теле функции main - это функция stdio_init_all без параметров. Вторая функция в теле функции main - это функция while с параметром true. У функции while есть тело и оно, в свою очередь, содержит 2 функции - printf и sleep_ms. Функция printf содержит в качестве параметра строку "Hello World!\n". Функция sleep_ms имеет в качестве параметра число 1000.

Следует заметить, что функция while с параметром true создаёт бесконечный цикл в котором бесконечно будет выполняться тело цикла.

Подытожим:

  1. при старте программы листинг 2 автоматически запускается функция main без параметров,
  2. в ней, по имени вызывается функция stdio_init_all без параметров,
  3. когда завершит работу функция stdio_init_all, запускается функция while с параметром true,
  4. в функции while первой запускается по имени функция printf("Hello World!\n"),
  5. после неё запускается функция sleep_ms(1000),
  6. на этом тело функции while(true) закончилось, но так как while(true) - это бесконечный цикл, переходим к строке 4 и повторяем все до строки 6, и так бесконечно!

Как работают функции в нашей программе

Функция printf включена в стандартную библиотеку языка Си stdio, функции stdio_init_all и sleep_ms из библиотеки pico/stdlib, функция while входит в ядро языка Си.

Задание. Найдите в программе листинг 2 строку в которой подключается стандартная библиотека stdio.

Задание. В программе лист. 2 мы используем 5 функций, перечислите их имена.

В программе лист. 2. мы почти досконально разобрались как работает функция main, немного познакомились с функцией while, наверное вы поняли что функция printf отправляет через последовательный порт (serial) на монитор последовательного порта (serial monitor) текстовое сообщение "Hello World!\n" переданное в функцию printf в круглых скобках, как аргумент.

Разберёмся, что делают функции stdio_init_all и sleep_ms.

sleep_ms - создаёт задержку в выполнении программы, приостанавливает работу процессорного ядра микроконтроллера на количество миллисекунд указанное в параметре функции. sleep_ms(1000) создаёт задержку в 1 секунду.

stdio_init_all - настраивает USB-порт платы Raspberry Pi Pico как виртуальный последовательный порт. Дело в том, что USB и Serial (COM) порты передают информацию по 2-м проводам, но протоколы передачи у них разные и если предварительно не согласовать эти порты и протоколы, то функция prinf ничего не сможет передать в USB порт, а компьютер ничего не сможет прочитать из USB порта.

Комментарии

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

Символы // начинают однострочный комментарий. Компилятор игнорирует текст начиная с символов // и до конца строки. За символам // начинается текст (комментарий) для человека (программиста) и никак не используется компьютером.

С символов /* начинается многострочный комментарий, а символами */ он заканчивается. Текст многострочного комментария может располагаться на нескольких строках в программе на языке Си.

/*
 * Программа "Hello World" для Raspberry Pi Pico.
 * Вывод сообщения "Hello World!" через USB порт
 * в Serial Monitor по 1 строке каждую секунду.
 * Используются библиотеки stdio и stdlib.
 */

// Включение библиотек
#include <stdio.h>
#include "pico/stdlib.h"

int main()                           // Точка входа в программу
{
    stdio_init_all();                // Инициализация USB как Serial

    while (true) {
        printf("Hello, world!\n");
        sleep_ms(1000);              // Задержка 1 сек.
    }
}

Лист. 3. Программа "Hello World" с комментариями.

Аргументы функций

Функция sleep_ms в программе листинг 3 вызывается с аргументом 1000. 1000 - это число миллисекунд, время на которое необходимо приостановить программу. Время выполнения большинства операций в микроконтроллере RP2040 измеряется в наносекундах, функция printf выполняется медленнее других операций, так как ей необходимо время для передачи текстовой информации в компьютер, но всё же довольно быстро. Нам такая скорость вывода текста не нужна, обычный человек с такой скоростью читать не может. После вывода каждой строки "Hello World!" запускается функция sleep_ms(1000) и пока она выполняется 1000 ms = 1 c текст не выводится.

Задание. Поэкспериментируйте с аргументом функции sleep_ms.

Функция printf вызывается с аргументом "Hello World!\n". Очевидно, аргумент функции printf не число, это последовательность символов в кавычках. Последовательность символов в кавычках программисты называют символьной строкой или строковая константа.

Вы уже заметили что из строки символов "Hello World!\n" не все символы печатаются. Не печатаются двойные кавычки потому, что это специальные символы. Первая двойная кавычка называется открывающей, она указывает, где в программе на языке Си начинается строковая константа, а вторая кавычка называется закрывающей, она указывает, где в программе на языке Си заканчивается строковая константа.

Так же, из строки символов "Hello World!\n" не печатается символ \ обратная наклонная черта (обратный слэш) и следующий за ним символ. Обратный слэш -  это так же специальный символ. В одиночку обратный слэш не используется, за ним следует символ, который что нибудь значат при выводе на экран монитора последовательного порта. Символ \ и следующий за ним символ называется Escape последовательностью. Escape последовательность - это управляющий печатью символ. Ниже приводим примеры Escape последовательностей:

\n Переход на новую строку

\t Горизонтальная табуляция

\v Вертикальная табуляция

\" Двойная кавычка

\' Одинарная кавычка

Программа лист. 4 выводит сообщение "Hello World!" в кавычках.

#include <stdio.h>
#include "pico/stdlib.h"


int main()
{
    stdio_init_all();

    while (true) {
        printf("\"Hello, world!\"\n");
        sleep_ms(1000);
    }
}

Лист. 4. Программа выводит сообщение в кавычках.

/*
 * Программа "Hello World" для Raspberry Pi Pico
 * Демонстрация базовой работы с USB-портом
 * 
 * Основные концепции:
 * - Инициализация периферии
 * - Работа с USB как последовательным портом
 * - Использование бесконечного цикла в embedded-системах
 */

// Стандартная библиотека ввода/вывода (нужна для printf)
#include <stdio.h>

// Библиотека специфичная для Pico (содержит функции инициализации и задержки)
#include "pico/stdlib.h"

// Главная функция - точка входа в программу
int main()
{
    // Инициализация USB для работы как последовательный порт
    // БЕЗ ЭТОЙ ФУНКЦИИ ПРОГРАММА НЕ СМОЖЕТ ВЫВОДИТЬ ДАННЫЕ НА КОМПЬЮТЕР!
    stdio_init_all();
    
    /*
     * Почему важна следующая задержка?
     * При подключении Pico к компьютеру требуется время (1-3 секунды),
     * чтобы операционная система распознала USB-устройство.
     * Без этой задержки первые сообщения могут быть потеряны!
     */
    sleep_ms(2500);  // 2500 миллисекунд = 2.5 секунды
    
    // Выводим информационное сообщение о начале работы
    // Используем \n\n чтобы "очистить" буфер терминала при подключении
    printf("\n\n--- Pico Hello World Program Started ---\n");

    /*
     * Бесконечный цикл - особенность embedded-программирования!
     * В отличие от программ на ПК, микроконтроллерные программы
     * никогда не должны завершать свою работу.
     */
    while (true) {
        // Выводим текст в последовательный порт (через USB)
        // \n - символ новой строки (LF - Line Feed)
        printf("Hello, world!\n");
        
        /*
         * Функция задержки - приостанавливает выполнение программы
         * Аргумент - время в миллисекундах (1000 мс = 1 секунда)
         * 
         * Важно: во время сна процессор может переходить
         * в режим пониженного энергопотребления
         */
        sleep_ms(1000);  // Пауза 1 секунда
        
        /*
         * Что происходит в цикле:
         * 1. Выводим сообщение
         * 2. Ждем 1 секунду
         * 3. Повторяем бесконечно
         * 
         * Без этого цикла программа завершилась бы сразу после первого вывода,
         * и USB-порт перестал бы работать!
         */
    }
    
    // Этот код никогда не выполнится из-за бесконечного цикла
    // Но формально возвращаем 0 (код успешного завершения)
    return 0;
}

Лист. 5. Программа "Hello World" доработанная и прокомментированная ИИ DeepSeek.