Продолжаем работу с сопроцессором ULP и на данном уроке мы попробуем поработать с устройством, взаимодействующим с контроллером по шине 1-Wire. Тем самым мы научимся переключать режимы работы ножки порта GPIO прямо в глубоком спящем режиме при помощи кода, работающего под управлением сопроцессора ULP.
В качестве подопытного устройства мы будем использовать датчик температуры DS18B20, с которым мы неоднократно работали, используя различные контроллеры, что позволит нам гораздо проще справиться с дальнейшей задачей — получить данные о температуре в режиме глубокого сна и затем этими данными пользоваться после пробуждения.
Схема урока будет состоять из отладочной платы с контроллером ESP32, к которой мы подключим датчик температуры DS18B20. Также мы помним, что для корректной работы с шиной потребуется резистор на 4,7 килоома, подключенный между сигнальным проводом датчика и шиной питания 3,3 вольта. Сигнальный провод датчика мы подключим к ножке порта GPIO4. Резистор мы установим на макетной плате и для начала подключим макетную плату к отладочной
Подключим датчик к макетной плате. Датчик находится на проводе в металлическом корпусе. С таким датчиком мы уже работали
Также для отслеживания результатов нашей работы подключим логический анализатор. Также подключим нашу отладочную плату к компьютеру
Схема урока у нас такая же как в уроке 37, даже остались перемычки для подключения дополнительных датчиков на ту же шину. Только мы будем пока работать с одним датчиком. С несколькими пока в планы не входит, но если вдруг возникнет потребность и будут подобные просьбы, то мы обязательно попытаемся подключить и завести ещё дополнительные датчики, то мы к данной теме вернёмся, поэтому перемычки я пока снимать не стал.
Проект был создан на основе проекта прошлого урока с именем ULP_FSM_BUTTON и получил новое имя ULP_FSM_ONEWIRE_INIT.
Откроем наш проект в Espressif IDE и для начала в функции init_ulp_program файла main.c мы изменим объявление ножек, вернее вместо объявления двух ножек у нас будет объявление одной
gpio_num_t gpio_one_wire_num = GPIO_NUM_4;
Определяем мы номер ножки RTC тоже только для одной ножки
int rtcio_one_wire_num = rtc_io_number_get(gpio_one_wire_num);
assert(rtc_gpio_is_valid_gpio(gpio_one_wire_num) && "GPIO must be an RTC IO");
Настраиваем ножку пока для начала на вход
rtc_gpio_init(gpio_one_wire_num);
rtc_gpio_set_direction(gpio_one_wire_num, RTC_GPIO_MODE_INPUT_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);
Далее также меняем имя переменной
uint32_t bit = rtcio_one_wire_num + 14;
Идём теперь в ассемблерный файл ulp_assembly_source_file.S и изменим там также имена команд в секции data
1 2 3 4 5 6 7 8 9 |
.global set_one_wire_pin set_one_wire_pin: WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_ENABLE_W1TS_S + 0, 1, 1) ret .global clear_one_wire_pin clear_one_wire_pin: WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_ENABLE_W1TC_S + 0, 1, 1) ret |
Команду для получения уровня ножки пока не добавляем. Будем работать с метками прямо в основном коде.
Также выше добавим ещё две команды, которые настраивают ножку на вход или выход (команда установки бита настраивает на выход, сброса — на вход)
1 2 3 4 5 6 7 8 9 10 |
.data .global set_out_one_wire_pin set_out_one_wire_pin: WRITE_RTC_REG(RTC_GPIO_ENABLE_W1TS_REG, RTC_GPIO_ENABLE_W1TS_S + 0, 1, 1) ret .global set_in_one_wire_pin set_in_one_wire_pin: WRITE_RTC_REG(RTC_GPIO_ENABLE_W1TC_REG, RTC_GPIO_ENABLE_W1TC_S + 0, 1, 1) ret |
Вернёмся в функцию init_ulp_program файла main.c и вместо всего этого
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_clear_led_pin&=0xf003ffff; // mask off bits 18-27
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_set_out_one_wire_pin&=0xf003ffff; // mask off bits 18-27
ulp_set_out_one_wire_pin|=(bit<<18)|(bit<<23); // modify from and to bit to read
ulp_set_in_one_wire_pin&=0xf003ffff; // mask off bits 18-27
ulp_set_in_one_wire_pin|=(bit<<18)|(bit<<23); // modify from and to bit to read
ulp_set_one_wire_pin&=0xf003ffff; // mask off bits 18-27
ulp_set_one_wire_pin|=(bit<<18)|(bit<<23); // modify from and to bit to read
ulp_clear_one_wire_pin&=0xf003ffff; // mask off bits 18-27
ulp_clear_one_wire_pin|=(bit<<18)|(bit<<23); // modify from and to bit to read
Идём опять в файл ulp_assembly_source_file.S и удалим данный код
//delay(10 msec)
move r0, 10
psr
jump delay_ms
psr
jump set_led_pin
read_button_loop:
//delay(10 ms)
move r0, 10
psr
jump delay_ms
psr
jump get_button_pin
jumpr read_button_loop, 1, eq
psr
jump clear_led_pin
//delay(1 sec)
move r0, 1000
psr
jump delay_ms
psr
jump set_led_pin
Останется у нас получение адреса стека, задержка на 4 секунды, выход из спящего режима и процедура задержки.
Также давайте добавим макросы для наших самодельных команд, чтобы наш дальнейший код стал ещё читабельней
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
.macro ret sr=r1 pop \sr jump \sr .endm .macro GPIO_IN psr jump set_in_one_wire_pin .endm .macro GPIO_OUT psr jump set_out_one_wire_pin .endm .macro GPIO_HIGH psr jump set_one_wire_pin .endm .macro GPIO_LOW psr jump clear_one_wire_pin .endm |
Добавим метку для процедуры получения температуры с датчика с возвратом
1 2 3 4 |
move r3, stackEnd get_temp: ret |
Выше добавим ещё одну процедуру для сброса устройства, подключенного к шине, в которой для начала настроим нашу ножку на выход
1 2 3 4 5 |
move r3, stackEnd rst_pulse: GPIO_OUT ret |
Вызовем данную процедуру в процедуре get_temp
1 2 3 |
get_temp: psr jump rst_pulse |
А процедуру get_temp вызовем в основном коде
1 2 3 4 |
move r3, stackEnd psr jump get_temp |
Теперь нам надо обеспечить прыжок через все наши процедуры, чтобы нам не войти в них из основного кода. Для этого добавим метку перед задержкой на 4 секунды
1 2 |
exit_loop: //delay(4 sec) |
И перейдём на данную метку в основном коде
1 2 3 4 5 6 |
move r3, stackEnd psr jump get_temp jump exit_loop |
Продолжим работать с функцией сброса rst_pulse, в которой после настройки ножки на выход прижмём её к общей шине, установив низкий уровень
1 2 |
GPIO_OUT GPIO_LOW |
И через примерно 500 микросекунд (не менее 480 микросекунд согласно документации, вычисляется по частоте работы сопроцессора) поднимем обратно
1 2 3 |
GPIO_LOW wait(4350) //>500 us GPIO_HIGH |
Давайте проверим, что у нас меняется уровень ножки в программе логического анализа. Тем самым мы определим, откликнулся ли наш датчик. Если датчик есть, то он прижмёт ножку к общей шине. Время ожидания датчика согласно технической документации — от 15 до 60 микросекунд. Поэтому после поднятия ножки настроим её на вход и подождём примерно 30 микросекунд
1 2 3 |
GPIO_HIGH GPIO_IN wait(240) //30us |
Настройку шины 1-wire производим так же, как во всех уроках по датчику DS18b20. Соберём наш код, прошьём контроллер и посмотрим результат
Всё отлично! Мало того, что мы видим наш импульс. Мы также видим и ответ нашего датчика.
Вернёмся в наш код и попробуем отследить ответ от датчика. В принципе, это не обязательно, можно просто переждать нужное время и продолжить с ним работать, но тогда мы не достигнем цели урока — изменение режима работы ножки в процессе глубокого сна.
После задержки отследим уровень нашей ножки
1 2 3 4 |
wait(240) /* 30us */ .global get_pin0 get_pin0: READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + 0, 1) // r0 holds input result |
Метка добавлена для того, чтобы при настройке менять номер ножки аналогично тому, как мы делали в секции data.
Перейдём в функцию init_ulp_program файла main.c и настроим ножку для данной команды
1 2 3 4 |
ulp_clear_one_wire_pin&=0xf003ffff; // mask off bits 18-27 ulp_clear_one_wire_pin|=(bit<<18)|(bit<<23); // modify from and to bit to read ulp_get_pin0&=0xf003ffff; // mask off bits 18-27 ulp_get_pin0|=(bit<<18)|(bit<<23); // modify from and to bit to read |
Вернёмся в файл ulp_assembly_source_file.S и в секции bss добавим переменную для хранения значения регистра r0
1 2 3 4 5 6 |
stackEnd: .long 0 .global dataR0 dataR0: .long 0 |
Сохраним значение регистра r0 в данной переменной в процедуре rst_pulse
1 2 3 |
READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + 0, 1) // r0 holds input result move r2, dataR0 st r0, r2, 0 |
Подождём примерно 470 микросекунд. Это необходимое время, через которое мы уже можем полноправно работать с датчиком и можем считать, что инициализация завершена
1 2 |
st r0, r2, 0 wait(3760) //470us |
В функции app_main файла main.c покажем значение нашей переменной в терминале
1 2 |
printf("ULP wakeup\n"); printf("R0: %5d\n", ulp_dataR0 & UINT16_MAX); |
Соберём код, прошьём контроллер и посмотрим результат в терминале после пробуждения контроллера
Результат — ноль, значит уровень низкий.
Отсоединим датчик от схемы, тем самым имитируя его неработоспособность или отсутствие
Регистр примет значение 1, значит уровень высокий, то есть от датчика ответа нет
Как только мы подключим обратно датчик, регистр примет нулевое значение
Итак, на данном уроке нам удалось научиться менять режим работы ножек GPIO во время глубокого сна при помощи сопроцессора ULP, что позволило нам отследить наличие датчика DS18B20 на ножке. Дальнейшая задача — отправить команду в датчик и получить от него значение температуры.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP32 Следующий урок
Недорогие отладочные платы ESP32 можно купить здесь Недорогие отладочные платы ESP32
Недорогие отладочные платы ESP32/ESP32-C3/ESP32-S3 можно купить здесь Недорогие отладочные платы ESP32
Датчик температуры DS18B20 в экране с проводом можно приобрести здесь DS18B20
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добавить комментарий