Продолжаем работу с сопроцессором ULP (ultra-low-power processor или процессор со сверхнизким потреблением).
И также мы продолжаем работать с портом GPIO. Только на данном уроке мы попытаемся получить значение уровня на ножке GPIO, настроенной на вход. Мы оставим и светодиод, который мы на прошлом уроке подключили к ножке GPIO4 катодом и а анодом к питанию 3,3 вольта, а также воспользуемся кнопкой, уже присутствующей на плате и подключенной к GPIO0. Поэтому схема наша не изменится
Посмотрим, как на схеме подключена кнопка к GPIO0
Мы видим, что ножка GPIO0 постоянно подтянута к питанию, а кнопка при нажатии притягивает её к общему проводу. Поэтому при написании кода нужно об этом помнить.
Проект мы создадим на основе проекта прошлого урока с именем ULP_FSM_BLINK и назовём его ULP_FSM_BUTTON.
Откроем проект в Espressif IDE и для начала в файле main.c в функции init_ulp_program изменим имя ножки для светодиода, а также объявим ещё ножку, к которой подключена кнопка
gpio_num_t gpio_led_num = GPIO_NUM_4, gpio_button_num = GPIO_NUM_0;
Здесь также, соответственно, изменим имя
rtc_gpio_init(gpio_led_num);
rtc_gpio_set_direction(gpio_led_num, RTC_GPIO_MODE_OUTPUT_ONLY);
Настроим ножку кнопки
1 2 3 4 5 6 7 |
rtc_gpio_set_direction(gpio_led_num, RTC_GPIO_MODE_OUTPUT_ONLY); rtc_gpio_init(gpio_button_num); rtc_gpio_set_direction(gpio_button_num, RTC_GPIO_MODE_INPUT_ONLY); rtc_gpio_pulldown_dis(gpio_button_num); rtc_gpio_pullup_dis(gpio_button_num); rtc_gpio_hold_en(gpio_button_num); |
Перейдём в файл ulp_assembly_source_file.S и изменим время задержки на 10 милисекунд, чтобы при погружении в сон у нас сразу светодиод потухал
move r3, stackEnd
//delay(10 ms)
move r0, 10
Изменение уровня и следующую задержку убираем, чтобы светодиод у нас не горел перед тем, как мы начнём отслеживать уровень на ножке
GPIO_LOW
//delay(1 sec)
move r0, 1000
psr
jump delay_ms
Добавим метку
1 2 3 |
GPIO_HIGH read_button_loop: |
Добавим ещё задержку на 10 милисекунд, чтобы наши циклы не повторялись слишком часто
1 2 3 4 5 |
read_button_loop: //delay(10 ms) move r0, 10 psr jump delay_ms |
Теперь нам надо будет прочитать уровень ножки GPIO.
Для этого мы воспользуемся макросом READ_RTC_REG
Данный макрос считывает до 16 бит из rtc_reg[low_bit + bit_width — 1 : low_bit] в R0.
То есть в регистр r0 записывается число из регистрового поля, объявленного в параметре 2 шириной, объявленной в параметре 3 из регистра, объявленного в параметре 1.
Прочитаем 11 бит из поля RTCIO_RTC_GPIO_IN_NEXT регистра RTCIO_RTC_GPIO_IN_REG
1 2 |
jump delay_ms READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + 11, 1) |
11 бит мы читаем, так как ножке GPIO0 соответствует ножка 11 в RTC
Добавим переход
1 2 |
READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + 11, 1) jumpr read_button_loop, 1, eq |
В случае единицы идём на метку, так как единица у нас будет при высоком уровне ножки, значит кнопка притянута к шине питания (не нажата).
При нажатии у нас кнопка притянется к земле и мы выйдем из цикла, так как наше условие будет ложным. В данном случае мы зажжём светодиод, подождём секунду и погасим его
1 2 3 4 5 6 7 8 9 10 |
jumpr read_button_loop, 1, eq GPIO_LOW //delay(1 sec) move r0, 1000 psr jump delay_ms GPIO_HIGH |
А далее по коду у нас 4-секундная задержка и возврат из спящего режима (пробуждение).
Соберём код, прошьём контроллер, включим терминал.
Мы ушли в спящий режим и больше из него не выходим
Нажмём и отпустим кнопку и наш светодиод при нажатии кнопки тут же зажжётся
Через секунду светодиод погаснет и ещё через четыре секунды мы вернемся из спящего режима, после чего опять уйдём в него
Если мы опять нажмём кнопку, то цикл повторится.
Интересно было бы после отслеживания нажатия отследить затем и отжатие. Но в этом ничего сложного нет и урок не об этом. Есть у меня задумка куда интереснее.
Раз уж у нас так быстро получилось отследить уровень на ножке, то давайте реализуем вот какую идею. А вдруг у нас светодиод будет подключен не к GPIO4, а также кнопка не к GPIO0. Нам придётся тогда менять номера ножек не только в их объявлении, но также смотреть в таблицу и просчитывать, какие ножки соответствует другим ножкам в RTC. Хотелось бы как-то это всё автоматизировать, чтобы нам нужно было бы поменять номера ножек только в их объявлении.
Оказалась эта задача не такая уж и лёгкая. В сети очень мало информации на этот счёт. Самое тяжёлое то, что макросы READ_RTC_REG и WRITE_RTC_REG не умеют работать ни с переменными, ни с регистрами общего назначения, а работают только с константами. Но реализовать идею всё же удалось.
Давайте сначала вычислим программно соответствие ножкам GPIO ножек RTC GPIO.
Возвращаемся в функцию init_ulp_program файла main.c и помощью специальной функции найдём номер ножки для каждой из двух наших ножек в RTC
1 2 3 4 5 |
gpio_num_t gpio_led_num = GPIO_NUM_4, gpio_button_num = GPIO_NUM_0; int rtcio_led_num = rtc_io_number_get(gpio_led_num); assert(rtc_gpio_is_valid_gpio(gpio_led_num) && "GPIO must be an RTC IO"); int rtcio_button_num = rtc_io_number_get(gpio_button_num); assert(rtc_gpio_is_valid_gpio(gpio_button_num) && "GPIO must be an RTC IO"); |
Так как наши регистровые поля RTC_GPIO_OUT_DATA_W1TC_S, RTC_GPIO_OUT_DATA_W1TS_S и RTC_GPIO_IN_NEXT_S в соответствующих им регистрах все располагаются с бита 14, то объявим переменную для номера бита, соответствующую пока что ножке светодиода
1 2 3 |
rtc_gpio_hold_en(gpio_button_num); uint32_t bit = rtcio_led_num + 14; |
Макрос WRITE_RTC_REG на самом деле использует команду REG_WR
Перейдём в файл ulp_assembly_source_file.S, объявим после секции bss секцию data и добавим туда метку с шаблоном команды записи в регистр, который устанавливает уровни ножек, установив высокий уровень в ножку под номером 0. Только после команды не забываем добавить команду возврата по адресу вызова
1 2 3 4 5 6 7 8 9 |
stackEnd: .long 0 .data .global set_led_pin set_led_pin: WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_ENABLE_W1TS_S + 0, 1, 1) ret |
Теперь наша задача вместо +0 записать другое значение.
Идём обратно в функцию init_ulp_program файла main.c и, используя описание команды а также операцию сдвигов, установим соответствующий бит в команде
1 2 3 4 |
uint32_t bit = rtcio_led_num + 14; ulp_set_led_pin&=0xf003ffff; // mask off bits 18-27 ulp_set_led_pin|=(bit<<18)|(bit<<23); // modify from and to bit to read |
В файле ulp_assembly_source_file.S добавим также команду записи в регистр сброса уровня ножек, также команду считывания поля регистра
1 2 3 4 5 6 7 8 9 10 11 12 |
WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_ENABLE_W1TS_S + 0, 1, 1) ret .global clear_led_pin clear_led_pin: WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_ENABLE_W1TC_S + 0, 1, 1) ret .global get_button_pin get_button_pin: READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + 0, 1) ret |
Возвращаемся опять в функцию init_ulp_program файла main.c и также занесём значение нужного бита и во вторую команду записи регистра
1 2 3 |
ulp_set_led_pin|=(bit<<18)|(bit<<23); // modify from and to bit to read ulp_clear_led_pin&=0xf003ffff; // mask off bits 18-27 ulp_clear_led_pin|=(bit<<18)|(bit<<23); // modify from and to bit to read |
Также вычислим номер бита для чтения ножки и занесём его в команду чтения регистра
1 2 3 4 5 |
ulp_clear_led_pin|=(bit<<18)|(bit<<23); // modify from and to bit to read bit = rtcio_button_num + 14; ulp_get_button_pin&=0xf003ffff; // mask off bits 18-27 ulp_get_button_pin|=(bit<<18)|(bit<<23); // modify from and to bit to read |
В файле ulp_assembly_source_file.S теперь можем удалить макросы установки уровней на ножке
.macro GPIO_LOW
WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + 10, 1, 1)
.endm
.macro GPIO_HIGH
WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 10, 1, 1)
.endm
Удалим вызов макроса GPIO_HIGH в двух местах кода и вместо этого в этих местах вызовем наши новые команды
psr
jump set_led_pin
А вместо вызова макроса GPIO_LOW вызовем также соответствующую команду
psr
jump clear_led_pin
Вместо строки
READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + 11, 1)
вызовем также соответствующую команду
psr
jump get_button_pin
Соберём код, прошьём контроллер, чтобы убедиться, что у нас также всё работает.
Давайте теперь проверим, что у нас при изменении ножек будет по-прежнему всё работать как надо.
Перебросим провод с ножки GPIO4 на ножку GPIO13
Изменим номер ножке в функции init_ulp_program файла main.c
gpio_num_t gpio_led_num = GPIO_NUM_13, gpio_button_num = GPIO_NUM_0;
Соберём код, прошьём контроллер и убедимся, что программа наша также успешно работает.
Теперь подключим внешнюю кнопку к GPIO4, соблюдая ту же схему, как и подключена внутренняя кнопка, то есть предварительно подключив ножку GPIO4 к шине питания через резистор 10 килоом
Изменим номер в объявлении
gpio_num_t gpio_led_num = GPIO_NUM_13, gpio_button_num = GPIO_NUM_4;
Соберём код, прошьём контроллер и проверим работоспособность нашего кода
Итак, на данном уроке нам удалось при помощи сопроцессора ULP поработать с ножкой порта GPIO, настроенной на вход, тем самым отследив уровень на ней, изменяемый с помощью кнопки, а также в ходе написания кода мы научились вносить изменения в команды.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP32 Следующий урок
Недорогие отладочные платы ESP32 можно купить здесь Недорогие отладочные платы ESP32
Недорогие отладочные платы ESP32/ESP32-C3/ESP32-S3 можно купить здесь Недорогие отладочные платы ESP32
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добавить комментарий