Продолжаем работу с сопроцессором 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 можно купить здесь:
На AliExpress Недорогие отладочные платы ESP32
На Яндекс.Маркет Недорогие отладочные платы ESP32
Датчик температуры в экране с проводом можно приобрести здесь:
На AliExpress DS18B20 в экране с проводом
На Яндекс.Маркет DS18B20 в экране с проводом
Логический анализатор 16 каналов можно приобрести (AliExpress) здесь
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добавить комментарий