<
本帖最后由 zhanzr21 于 2022-8-17 19:03 编辑
#申请原创#
上一篇文章中讲到DSP作为LKS32AT085的外设, 这个DSP还可以独立运行作为协处理器工作. 它与LKS32AT085的主核心CortexM0的区别如下:
1. 需要Cortex M0初始化代码和数据, 无法自动从Flash启动
2. 本身无中断, 可以引起Cortex M0的中断
3. 无Stack概念, 即DSP上运行的代码不能调用函数.
除此之外, 这个DSP属于凌欧的自主指令集, 目前是没有高级语言编译器的, 只有一个汇编器+模拟器的工具. 目前看来在上面开发程序还是比较费人工的, 希望厂家继续提高此处的开发者体验.
DSP程序运行基本模式
首先DSP中的代码不能自动启动, 需要Cortex M0初始化Code和data段. 一旦运行起来, Cortex M0即可以去做其他工作. 等DSP上的代码运行完了, 可以通过指令发起Cortex M0的外设中断, Cortex M0可以来取数据以及初始化下一次的运算.
这里以一个小例子来看看基本的DSP代码开发与使用: arctan和直角三角形求斜边长度的运算
DSP有一个数学函数, 输入直角三角形的两边(X,Y), 可以求取夹角以及斜边长度.
DSP作为外设
代码如下:
- typedef struct str_arctan_result {
- uint16_t arctan;
- uint16_t mod;
- } arctan_result;
- typedef struct str_arctan_input {
- uint16_t x;
- uint16_t y;
- } arctan_input;
- arctan_result cpu_issue_arctan_mod(arctan_input input) {
- DSP_SC &= (~BIT2);
- DSP_CORDIC_X = input.x;
- DSP_CORDIC_Y = input.y;
- arctan_result res;
- res.arctan = DSP_CORDIC_ARCTAN;
- res.mod = DSP_CORDIC_MOD;
- return res;
- }
验证一下结果:- DSP as a periph delta:0
- 2AAB, 2002
角度: 0x2AAB/0x7FFF = 0.33335367900631735 大约是π/3, 也就是60度.
斜边长: 0x2002 约等于0x1000的两倍. 可以看出是个30度,60度的直角三角形.
DSP单独运行
首先要写DSP上运行的代码
- LDRDHI R3 R4 0x0
- ARCTAN R5 R3 R4
-
- # insert dummy inst to wait for arctan finish
- ADD R0 R0 R0
- ADD R0 R0 R0
- ADD R0 R0 R0
- ADD R0 R0 R0
- ADD R0 R0 R0
- ADD R0 R0 R0
- ADD R0 R0 R0
- ADD R0 R0 R0
- ADD R0 R0 R0
- ADD R0 R0 R0
- ADD R0 R0 R0
- ADD R0 R0 R0
- ADD R0 R0 R0
- ADD R0 R0 R0
- ADD R0 R0 R0
- # arctan is stored at 0x5 in dsp's data mem
- STRWI R5 0x5
- # module is stored at 0x6 in dsp's data mem
- STRWI R6 0x6
- IRQ
- ADD R0 R0 R0
- ADD R0 R0 R0
- ADD R0 R0 R0
- ADD R0 R0 R0
将以上代码保存为arctan_simple.code, 其中arctan_simple为工程名
再准备一个DSP的数据文件, 保存为arctan_simple.data, 内容如下:
其中0x10001BB6即为要运算的X,Y值. 0x00000000为对齐值, 实际使用中如果发现不补齐, 运算出问题. 原因文档中没有找到.
将上述两个文件放在模拟器相同的目录, 修改工程配置文件:
再运行模拟器(此处模拟器其实是模拟器+汇编器二合一).
- $ ./LKS081DSP_Emulator.exe
- LKS081 DSP Emulator v1.1
- Released on 2020-3-9
- Author: zhangwl@linkosemi.com
- Copyright. 2020 LINKO Semiconductor Co.,Ltd. All Rights Reserved.
- Current project is <arctan_simple>.
- Reading in <arctan_simple.code>...
- <arctan_simple.code> contains 24 asm instructions
- Reading in <arctan_simple.data>...
- <arctan_simple.data> contains 2 words
- 2022-08-17 11:38:53
- <arctan_simple> Emulation is starting ...
- Info: at Line 24: IRQ
- IRQ generated and DSP halted when PC = 19.
- <arctan_simple> Emulation Finished
- 3132us elapsed on Emulator.
- 24Cycles elapsed on DSP.
- Final GPR snapshot is as below:
- PC = 0x0000
- R0 = 0x00000000
- R1 = 0x00000000
- R2 = 0x00000000
- R3 = 0x00001000
- R4 = 0x00001bb6
- R5 = 0x00002aa0
- R6 = 0x00001ff0
- R7 = 0x00000000
- 请按任意键继续. . .
以上操作等于 汇编+模拟. 在同级目录下面可以看到output目录, 里面有模拟的日志和汇编出来的二进制代码.
二进制代码和上述.data文件拷贝到MCU的工程中, 就可以对DSP做初始化了.
Note: 对于同样的输入, 模拟器运行的结果与DSP作为外设时不一致.
0x2AAB vs 0x2AA00x2002 vs 0x1FF0
貌似误差很小, 其实是需要凌鸥的工程师重点看看, 误差从何而来.
打开output目录中arctan_simple.hex
- 0x0000f01c,
- 0x0000792b,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x0000b145,
- 0x0000b186,
- 0x0000e000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
这就是DSP要运行的二进制代码, 拷贝到MCU工程中去, 使之成为一个const的数组.
- const uint32_t test_code_arctan_simple[] = {
- 0x0000f01c,
- 0x0000792b,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x0000b145,
- 0x0000b186,
- 0x0000e000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- 0x00000000,
- };
另外上文的.data文件也需要类似处理:
- const uint32_t test_data_arctan_simple[] = {
- 0x10001BB6,
- 0x00000000,
- };
这一点实在想吐槽, 为啥生成代码时不能自动生成C语言的数组呢, 希望凌鸥的工程师能发发力, 把细节做好一点.
以下是MCU中初始化DSP的代码与数据的代码样例, 为简便没有使能DSP的中断, 使用了不断poll标志位的方式.
- arctan_result dsp_issue_arctan_mod_poll(const uint32_t *code_ptr,
- const uint16_t code_u32_len,
- const uint32_t *data_ptr,
- const uint8_t data_u32_len) {
- volatile uint16_t arctan, mod;
- uint32_t i;
- // dsp data mem flush
- for (i = 0; i < 64; i++) {
- REG32(DSP_DATA_MEM_BASE + i * 4) = 0;
- }
- // dsp code mem flush
- for (i = 0; i < 512; i++) {
- REG32(DSP_CODE_MEM_BASE + i * 4) = 0;
- }
- // dsp data mem init
- for (i = 0; i < data_u32_len; i++) {
- REG32(DSP_DATA_MEM_BASE + i * 4) = data_ptr[i];
- }
- // dsp code mem init
- // code length 200 half word
- for (i = 0; i < code_u32_len; i++) {
- REG32(DSP_CODE_MEM_BASE + i * 4) = code_ptr[i];
- }
- // Pause DSP
- DSP_SC |= BIT1;
- // Reset DSP PC
- DSP_SC |= BIT2;
- // Clear the IRQ flag
- DSP_SC |= BIT0;
- // Start DSP
- DSP_SC = BIT0;
-
- // wait until irq set and dsp paused
- while ((BIT0) != (DSP_SC & (BIT0))) {
- __WFI();
- }
-
- arctan_result res;
- res.arctan = REG32(DSP_DATA_MEM_BASE + 5 * 4);
- res.mod = REG32(DSP_DATA_MEM_BASE + 6 * 4);
- return res;
- }
来看看结果:
- DSP standalone delta:1
- 2AAC, 2002
Note: 对于同样的输入, 模拟器运行的结果/DSP作为外设/DSP单独运行全部不一致.
0x2AAB vs 0x2AA0 vs 0x2AAC
0x2002 vs 0x1FF0 vs 0x2002
貌似误差很小, 但作者实在想知道究竟是哪个地方不精确呢.
希望官方工程师能注意到这些误差, 后面能做相应的更正.
DSP指令一览
如上所述, 这个DSP没有Stack的概念, 导致不能调用函数, 这个不算bug, 因为本身设计目标不是一个完整的内核, 而是辅助数**算的协处理器.
这里把它的指令集和与Cortex M0的指令集放在一起做一个简短的对比.
| Cortex M0指令集 | DSP指令集 | 整数运算 | ADCS | ADD | ADD | ADDI | ANDS | SUB | ASRS | ASR | CMN | ASRI | CMP | LSL | EORS | LSLI | LSLS | MAC | LSRS | MACI | MULS | DIV | ORRS | SAT | REV | SATI | REV16 | | REVSH | | RORS | | RSBS | | SBCS | | SUB | | SXTB | | SXTH | | TST | | BICS | | UXTB | | UXTH | | 三角函数 | | SIN_COS | | ARCTAN | 其他函数 | | SQRT | 数据存取 | ADR | LDRWI | LDM | LDRDHI | LDR | STRWI | LDRB | | LDRH | STRDHI | LDRSB | | LDRSH | | MOV | | MRS | | MSR | | PUSH | | POP | | STM | | STR | | STRB | | STRH | | 分支跳转 | B | JUMP | BX | JUMPI | | JLE | | JLEI | 控制 | BKPT | IRQ | WFI | NOP | WFE | | CPSID | | CPSIE | | DMB | | DSB | | ISB | | MVNS | | NOP | | SEV | | SVC | | 总体而言, 这个DSP内核缺乏Stack相关指令, 缺乏条件判断指令, 缺乏中断相关指令, 比起Cortex M0, 多了一些数学函数, 多了除法指令. 这也是符合本身的定位的.
本文完整的测试代码下载地址点这里.
|
|