ULP(Ultra Low Power 超低功耗)协处理器可以在主处理器处于深度睡眠模式时,使用 ADC、温度传感器和外部 I2C 传感器执行测量操作。ULP 协处理器可以访问 RTC_SLOW_MEM 内存区域及 RTC_CNTL、RTC_IO、SARADC 等外设寄存器。ULP 协处理器使用 32 位固定宽度的指令,32 位内存寻址,配备 4 个 16 位通用寄存器。
官方的资料还是比较丰富的。
访问ULP程序变量
在ULP程序中定义的全局符号可以在主程序中使用。
.global me_count //初始化me_count变量
me_count: .long 0
move r3, me_count
ld r3, r3, 0
在程序中引用 extern uint32_t ulp_me_count; //总是以ulp_开头来引用
在读取 ULP 写的变量时,主应用程序需要屏蔽高 16 位(ULP 程序在 RTC 内存中只能使用 32 位字的低 16 位,因为寄存器是 16 位。就是说ULP是通过RTC内存来进行数据交换。连ULP程序都是加载到RTC内存中)
printf(“Last measurement value: %d\n”, ulp_last_measurement & UINT16_MAX);
启动ULP程序
在menuconfig中必须启用“Enable Ultra Low Power Coprocessor"选项,以便为ULP预留内存。
extern const uint8_t bin_start[] asm("_binary_ulp_app_name_bin_start");
extern const uint8_t bin_end[] asm("_binary_ulp_app_name_bin_end");
void start_ulp_program() {
ESP_ERROR_CHECK( ulp_load_binary(
0 /* load address, set to 0 when using default linker scripts */,
bin_start,
(bin_end - bin_start) / sizeof(uint32_t)) );
}
这里的ulp_app_name在配置文件CMakeLists.txt匹配的。多个ulp时就需要修改了。
ulp_load_binary 用于将ulp载入rtc内存中。
一旦上述程序加载到 RTC 内存后,应用程序即可启动此程序,并将入口点的地址传递给 ulp_run 函数:
ESP_ERROR_CHECK( ulp_run(&ulp_entry - RTC_SLOW_MEM) );
urlp_run参数只有一个:入口点。象上方语句中的ulp_entry即entry变量,是在ulp代码中指定的。
代码中需要申请为全局标签。例如示例的tsens.S中:
....
.global entry
entry:
.....
ULP程序流程
ULP由定时器启动,调用ulp_run则启动此定时器。
程序中,使用 ulp_set_wakeup_period 函数来设置 ULP 定时器周期值,例如:
ulp_set_wakeup_period(0, 3*1000*1000); //设置0号寄存器(0-4)3秒唤醒
指令集
ULP协处理器具有4个16位通用寄存器,分别标记为R0,R1,R2,R3。 它还具有一个8位计数器寄存器(stage_cnt),可用于实现循环。
ULP协处理器可以访问RTC_SLOW_MEM存储区域的8k字节。 存储器以32位字为单位寻址。
所有指令均为32位。 跳转指令,ALU指令,外设寄存器和存储器访问指令在1个周期内执行。 根据外设的操作,与外设(TSENS,ADC,I2C)配合使用的指令需要可变的周期数。
指令语法不区分大小写。大写字母和小写字母可以随意使用和混合。对于注册名和指令名都是如此。
.global array
array: .long 0
.long 0
.long 0
.long 0
MOVE R1, array
MOVE R2, 0x1234
ST R2, R1, 0 // array[0]=R2
ST R2, R1, 4 // array[1]=R2 4字节偏移量
ADD R1, R1, 2 // 将地址增加2个字(8字节)
ST R2, R1, 0 // array[2]=R2
ADD 加
R3+R2->R1
1: ADD R1, R2, R3 //R1 = R2 + R3
2: Add R1, R2, 0x1234 //R1 = R2 + 0x1234
3: .set value1, 0x03 //constant value1=0x03
Add R1, R2, value1 //R1 = R2 + value1
4: .global label //变量标签声明
Add R1, R2, label //R1 = R2 + label
...
label: nop //变量标号定义
SUB 减
R2-R3->R1
1: SUB R1, R2, R3 //R1 = R2 - R3
2: sub R1, R2, 0x1234 //R1 = R2 - 0x1234
3: .set value1, 0x03 //constant value1=0x03
SUB R1, R2, value1 //R1 = R2 - value1
4: .global label //declaration of variable label
SUB R1, R2, label //R1 = R2 - label
....
label: nop //definition of variable label
AND 与
R2 & R3 -> R1
1: AND R1, R2, R3 //R1 = R2 & R3
2: AND R1, R2, 0x1234 //R1 = R2 & 0x1234
3: .set value1, 0x03 //constant value1=0x03
AND R1, R2, value1 //R1 = R2 & value1
4: .global label //declaration of variable label
AND R1, R2, label //R1 = R2 & label
...
label: nop //definition of variable label
OR 或
R2 | R3 -> R1
1: OR R1, R2, R3 //R1 = R2 | R3
2: OR R1, R2, 0x1234 //R1 = R2 | 0x1234
3: .set value1, 0x03 //constant value1=0x03
OR R1, R2, value1 //R1 = R2 | value1
4: .global label //declaration of variable label
OR R1, R2, label //R1 = R2 |label
...
label: nop //definition of variable label
LSH 逻辑左移
R2 « R3 -> R1
1: LSH R1, R2, R3 //R1 = R2 << R3
2: LSH R1, R2, 0x03 //R1 = R2 << 0x03
3: .set value1, 0x03 //constant value1=0x03
LSH R1, R2, value1 //R1 = R2 << value1
4: .global label //declaration of variable label
LSH R1, R2, label //R1 = R2 << label
...
label: nop //definition of variable label
RSH 逻辑右移
R2 » R3 -> R1
MOVE
1: MOVE R1, R2 //R1 = R2
2: MOVE R1, 0x03 //R1 = 0x03
3: .set value1, 0x03 //constant value1=0x03
MOVE R1, value1 //R1 = value1
4: .global label //declaration of label
MOVE R1, label //R1 = address_of(label) / 4
...
label: nop //definition of label
ST 将数据存储到内存
1: ST R1, R2, 0x12 //MEM[R2+0x12] = R1
2: .data //数据部分定义
Addr1: .word 123 // 定义 Addr1为16位的值123
.set offs, 0x00 // 定义常数 offs
.text //定义文本部分
MOVE R1, 1 // R1 = 1
MOVE R2, Addr1 // R2 = Addr1
ST R1, R2, offs // MEM[R2 + 0] = R1
// MEM[Addr1 + 0] will be 32'h600001
LD 从内存加载数据
1: LD R1, R2, 0x12 //R1 = MEM[R2+0x12]
2: .data //数据部分定义
Addr1: .word 123 // 定义 Addr1为16位的值123
.set offs, 0x00 // 定义常数 offs
.text //定义文本部分
MOVE R1, 1 // R1 = 1
MOVE R2, Addr1 // R2 = Addr1 / 4 (标签地址转换为字) ?
LD R1, R2, offs // R1 = MEM[R2 + 0]
// R1 will be 123
JUMP 跳转
1: JUMP R1 // 跳转到R1中的地址 (R1地址是32位字)
2: JUMP 0x120, EQ // 如果ALU结果为0,则跳转到地址0x120(以字节为单位)
3: JUMP label // 跳转到label
...
label: nop // 定义label
4: .global label // Declaration of global label
MOVE R1, label // R1 = label (value loaded into R1 is in words)
JUMP R1 // Jump to label
...
label: nop // Definition of label
JUMPR 相对位置跳转 (基于R0的条件)
跳转条件
EQ (equal) – jump if value in R0 == threshold
LT (less than) – jump if value in R0 < threshold
LE (less or equal) – jump if value in R0 <= threshold
GT (greater than) – jump if value in R0 > threshold
GE (greater or equal) – jump if value in R0 >= threshold
1:pos: JUMPR 16, 20, GE // Jump to address (position + 16 bytes) if value in R0 >= 20
2: //通过R0寄存器值,实现循环
MOVE R0, 16 // load 16 into R0
label: SUB R0, R0, 1 // R0--
NOP // do something
JUMPR label, 1, GE // jump to label if R0 >= 1
JUMPS 跳转到一个相对地址 (基于阶段计数器条件)
1:pos: JUMPS 16, 20, EQ // Jump to (position + 16 bytes) if stage_cnt == 20
2: // 使用阶段计数器实现循环
STAGE_RST // set stage_cnt to 0
label: STAGE_INC 1 // stage_cnt++
NOP // do something
JUMPS label, 16, LT // jump to label if stage_cnt < 16
STAGE_RST 重置阶段计数器
STAGE_INC 加阶段计数寄存器
STAGE_DEC 减阶段计数寄存器
HALT 结束程序
WAKE 芯片唤醒
指令从ULP发送一个中断到RTC控制器
1: is_rdy_for_wakeup: // 读取 RTC_CNTL_RDY_FOR_WAKEUP 位
READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP)
AND r0, r0, 1
JUMP is_rdy_for_wakeup, eq // 循环直到被置位(高电平)
WAKE // 唤醒
REG_WR 0x006, 24, 24, 0 // 停止ULP定时器 (清除 RTC_CNTL_ULP_CP_SLP_TIMER_EN)
HALT // 停止ULP程序
SLEEP 设置ULP周期唤醒的定时器
SLEEP 1 (0-4)
WAIT 等待(延时)
WAIT 10
TSENS 使用温度传感器进行测量
TSENS R1, 1000 // 测量温度传感器1000个循环,并将结果存储到R1
ADC 用ADC进行测量
ADC R1, 0, 1 // 使用ADC1 pad2测量值,并将结果存储到R1中
I2C_RD 从I2C从机读取单字节
I2C_RD 0x10, 7, 0, 0 // 读取从设备的子地址0x10的数据,设置到SENS_I2C_SLAVE_ADDR0
I2C_RD Sub_addr, High, Low, Slave_sel
Sub_addr 进行读取的I2C从站地址
High,Low 要读取的位范围
Slave_sel 要使用的I2C从设备地址的索引
I2C_WR 写一个字节到I2C从设备
I2C_WR 0x20, 0x33, 7, 0, 1 //使用在SENS_I2C_SLAVE_ADDR1中设置的地址将字节0x33写入从站的子地址0x20。
REG_RD 从外设寄存器读取
REG_RD 0x120, 7, 4 //load 4 bits: R0 = {12’b0, REG[0x120][7:4]}
REG_WR 写入外设寄存器
REG_WR 0x120, 7, 0, 0x10 //set 8 bits: REG[0x120][7:0] = 0x10
宏用于外设寄存器访问
soc/soc_ulp.h 文件中定义了一些宏
READ_RTC_REG(rtc_reg, low_bit, bit_width) 从RTC寄存器中读取最多16位到R0中
#include "soc/soc_ulp.h"
#include "soc/rtc_cntl_reg.h"
READ_RTC_REG(RTC_CNTL_TIME0_REG, 0, 16) /* Read 16 lower bits of RTC_CNTL_TIME0_REG into R0 */
READ_RTC_FIELD(rtc_reg, field) 从RTC寄存器中读取最多16位到R0寄存器中。
#include "soc/soc_ulp.h"
#include "soc/sens_reg.h"
/* Read 8-bit SENS_TSENS_OUT field of SENS_SAR_SLAVE_ADDR3_REG into R0 */
READ_RTC_FIELD(SENS_SAR_SLAVE_ADDR3_REG, SENS_TSENS_OUT)
WRITE_RTC_REG(rtc_reg, low_bit, bit_width, value) 将立即值写入RTC寄存器
WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 2, 1, 1)
WRITE_RTC_FIELD(rtc_reg, field, value)
WRITE_RTC_FIELD(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN, 0)