Продолжаем работу с сопроцессором ULP, также мы продолжаем работу с устройством, взаимодействующим с контроллером по шине 1-Wire, а именно с датчиком температуры DS18B20. Передавать данные в шину мы уже умеем, а на данном уроке мы попытаемся принять данные из устройства в контроллер и записать их в память контроллера.
Схема наша осталась та же, что и в прошлом уроке — отладочная плата с контроллером esp32, подключенный к ней датчик температуры DS18B20, а также логический анализатор, подключенный к сигнальной ножке датчика для анализа обмена данными по шине
Проект за основу мы также возьмём из прошлого урока с именем ULP_FSM_ONEWIRE_SEND и дадим ему новое имя ULP_FSM_ONEWIRE_READ.
Откроем наш проект в Espressif IDE, перейдём в файл ulp_assembly_source_file.S и ниже выхода из процедуры get_temp добавим ещё одну процедуру, которая будет читать байт из устройства и сохранять его в регистре R2
1 2 3 4 5 6 |
ret read_byte: move r2, 0 move r0, 0 ret |
Пока мы обнулили значения регистров R2 и R0.
Добавим метку, так как байт будет читаться побитно
1 2 |
move r0, 0 read_byte_loop: |
Ну, и ниже данной процедуры добавим процедуру для чтения одного бита из шины, в которой мы передадим короткий импульс в шину (опустим и поднимем ножку), настроив через некоторое время ножку на вход. Данный импульс заставит устройство передать очередной бит
1 2 3 4 5 6 7 8 9 10 11 12 |
ret //R0: (1bit) stores GPIO input on return. // since R2 is used to store all the bits read_bit: GPIO_OUT GPIO_LOW wait(10) //2 us GPIO_HIGH wait(100) //15 us GPIO_IN ret |
Узнаем уровень на ножке
1 2 3 4 |
GPIO_IN .global get_pin1 get_pin1: READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + 0, 1) |
Мы используем здесь также глобальную метку, так как после неё у нас находится только болванка для команды чтения регистра.
Поэтому теперь идём в функцию init_ulp_program файла main.c и установим нужный бит для нужной нам ножки
1 2 3 |
ulp_get_pin0|=(bit<<18)|(bit<<23); // modify from and to bit to read ulp_get_pin1&=0xf003ffff; ulp_get_pin1|=(bit<<18)|(bit<<23); |
Вернёмся в файл ulp_assembly_source_file.S и в процедуре read_byte вызовем процедуру чтения бита, сохранив перед этим в стек значение регистра R0
1 2 3 4 |
read_byte_loop: push r0 psr jump read_bit |
Скопируем значение регистра R0, в котором и хранится считанный бит, в регистр R1 и вернём из стека сохранённое перед вызовом процедуры значение регистра R0
1 2 3 |
jump read_bit move r1, r0 pop r0 |
Сдвинем влево значение регистра R1 в позицию, которую мы достигли в цикле, хранящуюся в регистре R0, и запишем результат обратно в регистр R1
1 2 3 |
pop r0 lsh r1, r1, r0 |
Инструкция сдвига влево подобна инструкции сдвига вправо
Инструкция LSH выполняет логический сдвиг влево от исходного регистра на количество битов из другого исходного регистра или 16-битного знакового значения и сохраняет результат в регистре назначения.
Затем мы при помощи логической операции ИЛИ добавляем значение бита в значение регистра R2, в котором постепенно набираются биты всего читаемого байта
1 2 |
lsh r1, r1, r0 or r2, r2, r1 |
Немного подождём перед чтением следующего бита, увеличим счётчик битов, который находится в регистре R0 и перейдём назад на метку, если мы ещё не достигли числа 8
1 2 3 4 5 6 |
or r2, r2, r1 wait(120) //15 us add r0, r0, 1 jumpr read_byte_loop, 8, LT |
В процедуре get_temp после передачи команды начала преобразования значения температуры в цифровой вид подождём 750 милисекунд, необходимых для завершения преобразования, и передадим команду чтения скратчпада (всей памяти датчика) устройству
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
move r2, 0x44 psr jump send_byte //delay 750ms, looping 750x in delay_ms //0x44 convert command: convertion is slow, 750 milliseconds //delay(4 sec) move r0, 750 psr jump delay_ms psr jump rst_pulse //sent 0xCC and 0xBE command move r2, 0xCC psr jump send_byte move r2, 0xBE //read scratchpad(9 bytes, last byte is CRC) psr jump send_byte |
Подготовим в секции bss место для хранения считанных байтов скратчпада
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
dataR0: .long 0 .global scratchpad_memory scratchpad_memory: // Return 9 Bytes of data after an 0xBE command. .long 0 // Byte 0: Temperature LSB .long 0 // Byte 1: Temperature MSB .long 0 // Byte 2: Th Register .long 0 // Byte 3: Tl Register .long 0 // Byte 4: Configuraion Register .long 0 // Byte 5: Reserved .long 0 // Byte 6: Reserved .long 0 // Byte 7: Reserved .long 0 // Byte 8: CRC value |
Вернёмся в процедуру get_temp и сохраним в регистр R0 адрес начала памяти, отведённой под скратчпад
1 2 3 4 5 6 |
move r2, 0xBE //read scratchpad(9 bytes, last byte is CRC) psr jump send_byte //read 9 bytes from scratchpad memory stage_rst move r0, scratchpad_memory |
Перед сохранением адреса памяти мы использовали новую для нас команду stage_rst, которая сбрасывает в 0 значение регистра-счётчика, который будет нам в дальнейшем необходим для условного перехода
Добавим метку, так как читать мы будем 9 байт
1 2 |
move r0, scratchpad_memory get_temp_9bytes: |
Сохраним в стеке значение регистра R0, прочитаем байт и вернём из стека значение регистра
1 2 3 4 5 |
get_temp_9bytes: push r0 psr jump read_byte pop r0 |
Сохраним в память значение байта и прибавим счётчик байтов, хранящийся в регистре R0
1 2 3 |
pop r0 st r2, r0, 0 add r0, r0, 1 |
При помощи следующей инструкции сопроцессора инкрементируем значение регистра-счётчика
1 2 |
add r0, r0, 1 stage_inc 1 |
Вот описание данной инструкции
Перейдём на нашу метку, если мы ещё не достигли значения 9 в регистре-счётчике
1 2 |
stage_inc 1 jumps get_temp_9bytes, 9, LT |
Инструкция JUMPS, подобно инструкции JUMPR, также осуществляет переход по условию, только пользуется она в условиях уже значением регистра-счётчика stage_cnt
Условия в JUMPS те же самые, что и в JUMPR.
Пошлём в шину команду перезагрузки
1 2 3 4 |
jumps get_temp_9bytes, 9, LT psr jump rst_pulse |
В основном коде после вызова процедуры get_temp сохраним значение адреса памяти со считанным скартчпадом в регистр R1
1 2 3 |
jump get_temp move r1, scratchpad_memory |
Далее при помощи серии инструкций сохраним значение самой первой ячейки скратчпада в переменную
1 2 3 4 |
move r1, scratchpad_memory ld r0, r1, 0 move r1, dataR0 st r0, r1, 0 |
Это будет у нас младший байт значения температуры.
Перейдём в файл main.c и в функции app_main отобразим в терминале значение нашего байта
1 2 |
printf("ULP wakeup\n"); printf("Data R0: 0x%04x\n", ulp_dataR0 & UINT16_MAX); |
Соберём код, прошьём контроллер, запустим терминал, а затем проанализируем обмен по шине в программе логического анализа
Посмотрим значение в терминале
Отлично! Показания сходятся. Только надо анализировать и смотреть в терминале одновременно, так как младший байт с изменением температуры воздуха может меняться.
0xBE — это команда чтения скратчпада, поэтому её не учитываем.
Прочитаем ещё какой-нибудь байт, например четвёртый (мы читали нулевой). Для это вернёмся в файл ulp_assembly_source_file.S и прибавим четыре к значению регистра, тем самым сдвинемся в памяти на 4 ячейки
1 2 |
move r1, scratchpad_memory add r1, r1, 4 |
Соберём код, прошьём контроллер, и посмотрим результат в терминале
Байт также сходится с байтом из логического анализа, где можно значения не перечитывать, так как это байт конфигурационного регистра, который не меняется.
Итак, на данном уроке мы научились считывать байты из устройства, подключенного к контроллеру ESP32 по шине 1-Wire, при помощи ассемблерного кода, работающего под управлением сопроцессора ULP. Также в процессе работы мы изучили ещё несколько ассемблерных инструкций сопроцессора ULP.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP32 Следующий урок
Недорогие отладочные платы ESP32 можно купить здесь Недорогие отладочные платы ESP32
Недорогие отладочные платы ESP32/ESP32-C3/ESP32-S3 можно купить здесь Недорогие отладочные платы ESP32
Датчик температуры DS18B20 в экране с проводом можно приобрести здесь DS18B20
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добавить комментарий