|
今天又有时间了,从RP2040手册3.6.8章节照葫芦画瓢扒来一个PWM控制器。对它做了一些改装以适配Arduino环境。对汇编代码也改了一些,不用样例中的“写立即执行代码进状态机,装载PWM周期”的方法。而是直接从TX fifo推入周期,让状态机最开始两行代码读到周期后永久保存在ISR里。
代码如下:
- #include <Arduino.h>
- #include "pico/stdlib.h"
- #include "hardware/pio.h"
- #include "hardware/gpio.h"
- // PIO 程序机器码
- const uint16_t pio_program_instructions[] = {
- 0x80a0, // 0: pull block
- 0x60c0, // 1: out isr, 32
- 0x9080, // 2: pull noblock side 0
- 0xa027, // 3: mov x, osr
- 0xa046, // 4: mov y, isr
- 0x00a7, // 5: jmp x != y, 7
- 0x1808, // 6: jmp 8 side 1
- 0xa042, // 7: nop
- 0x0085, // 8: jmp y--, 5
- 0x0002, // 9: jmp 2
- };
- // 定义 PIO 程序结构体
- const struct pio_program pio_program = {
- .instructions = pio_program_instructions,
- .length = 10, // 指令数量
- .origin = 0, // 分配起始地址
- };
- PIO pio = pio0; // 使用 PIO 0
- uint sm;
- uint pin = 2;
- uint period = 1000; //周期
- uint level = 0; //脉宽
- void setup() {
- //启用串口
- Serial.begin(115200);delay(2000);
- Serial.println("I AM BEGINNING!");
- // 初始化 GPIO 2 为 PIO 功能
- gpio_init(pin);
- pio_gpio_init(pio, pin);
- //gpio_set_function(pin, GPIO_FUNC_PIO0);
- // 初始化 PIO
- uint offset = pio_add_program(pio, &pio_program); // 加载 PIO 程序
- Serial.printf("Loaded program at %d\n", offset);
- sm = pio_claim_unused_sm(pio, true); // 分配一个状态机
- pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
- pio_sm_config c = pio_get_default_sm_config(); //获取一个默认状态机配置
- sm_config_set_wrap(&c, offset, offset + pio_program.length - 1); //设置PIO代码循环范围
- sm_config_set_sideset(&c, 2, true, false); //偷窃delay域2位做Sideset
- sm_config_set_sideset_pins(&c, pin);
- sm_config_set_clkdiv(&c, 1); // 设置时钟分频(200 MHz / 1 = 200 MHz)
- pio_sm_init(pio, sm, offset, &c);
- pio_sm_set_enabled(pio, sm, true); // 启用状态机
- //推入周期设置
- pio_sm_put_blocking(pio, sm, period);
- Serial.printf("Put period = %d\n", period);
- //不断更改脉宽
- while (true) {
- Serial.printf("Level = %d\n", level);
- pio_sm_put_blocking(pio, sm, level);
- level = (level + 20) % 500;
- delay(100);
- }
- // pio_sm_put_blocking(pio, sm, 200); //for debugging
- }
- void loop() {
- }
复制代码
在
sm_config_set_sideset(&c, 2, true, false);
这一行折腾了很久。原因是(手册给出的)样例程序中并不包含这一句。但是因为不明原因,在arduino环境下是必须设置这一行的。或许在github源头里是有的。另外这个函数的参数2,true,false也是需要倒腾一阵才搞定。这些参数是和汇编代码中的“.side_set 1 opt”有呼应关系的,刚开始没注意到。
还有一些不明白的地方,但是毕竟囫囵吞枣地让它转起来了。这就先凑合着吧。
例如,
pio_program.origin中的origin,
uint offset = pio_add_program(pio, &pio_program)中的offset,
汇编中的jmp指令,
这三者之间的关系我就不是很清楚。问了deepseek很多次,绕着圈地问,都没得到很准确的回答。
origin是指定汇编程序加载位置的,设为-1,就会由系统自动指定加载位置。我的程序中,会加载到offset=22那个地址。考虑到机器码是10条指令,那么看来系统倾向于把指令加载到32字指令空间的末尾。
但是,jmp指令可是在绝对地址里跳的。这么乱加载程序,应该会导致程序跳错地址。
然而实践中并不会跳错。
因为没搞懂,所以我指定了origin=0。加载后也会返回offset=0。
最后给出机器码结构体的汇编源文件及其注释:
- .program pwm
- .side_set 1 opt
- pull block ;Pull the period from FIFO to OSR then to ISR
- out isr,32
- period:
- pull noblock side 0 ; Pull from FIFO to OSR if available, else copy X to OSR.
- mov x, osr ; Copy most-recently-pulled value back to scratch X
- mov y, isr ; ISR contains PWM period. Y used as counter.
- countloop:
- jmp x!=y noset ; Set pin high if X == Y, keep the two paths length matched
- jmp skip side 1
- noset:
- nop ; Single dummy cycle to keep the two paths the same length
- skip:
- jmp y-- countloop ; Loop until Y hits 0, then pull a fresh PWM value from FIFO
- jmp period
复制代码
|
评分
-
1
查看全部评分
-
|