A Fast PWM for CSR101x

CSR101x是CSR的一款蓝牙低功耗芯片,在介绍PWM之前,这里先对它基本特征做一个简单的描述,

  • CSR101x内部有两个独立的MCU, 一个16位的主芯片XAP,一个8位的8051。
  • XAP工作在16M频率上,而8051能工作在16M和32KHz的频率上。
  • XAP能够送中断事件到8051,8051也能够送Wake-up事件到XAP。
  • 8051能使用XAP的PIO口,不过PIO同一时间只能被一方使用。
  • 它们有一个64Bytes的共享数据区 。
  • 8051有1K字节的代码区和64字节的数据区。

下面是它的内存示意图:

从CSR xIDE上可以得到如下宏定义:

#define PIO_CONTROLLER_RAM_START ((uint16*)0xE800)

#define PIO_CONTROLLER_DATA_WORD (PIO_CONTROLLER_RAM_START + 0x0020)


这两个定义对我们理解其两者的数据共享非常有用。下面这个API就是XAP用来设置PWM宽度的,其中就有用到这些定义,

 bool PioFastPwmSetWidth(uint8 pwm_port, uint8 bright_width, uint8 dull_width,
                        bool inverted)
{
    uint8 port = pwm_port;
    
    if( bright_width > 255 || dull_width > 255 )
    {
        return FALSE;
    }
    
    switch( pwm_port )
    {
    case PWM0_PORT:
        port = 0;
        break;
    case PWM1_PORT:
        port = 1;
        break;
    case PWM2_PORT:
        port = 2;
        break;
    case PWM3_PORT:
        port = 3;
        break;
    default:
        return FALSE;
    }

    uint16*address=PIO_CONTROLLER_DATA_WORD+(port>>1);

    if(port&1)
    {
        *address&=0x00ff;
        *address|=(bright_width<<8);
        address+=2;
        *address&=0x00ff;
        *address|=(dull_width<<8);
    }
    else
    {
        *address&=0xff00;
        *address|=bright_width;
        address+=2;
        *address&=0xff00;
        *address|=dull_width;
    }

    address=PIO_CONTROLLER_DATA_WORD+4;

    if(inverted)
        *address&=~(1<<port);
    else
        *address|=1<< port;
    return TRUE;
}


再来看看8051源代码:

;/******************************************************************************
; * FILE
; *    pio_ctrlr_code.asm
; *
; *  DESCRIPTION
; *    This file contains the low level 8051 assembly code for fast PWM
; *
; *  NOTICE
; *
; *****************************************************************************/
; 
; Local variables
.equ TEMP, 0x3e

; Shared memory from 0x40
; 0~3 BRIGHT duty cycles
; 4~7 DULL duty cycles
; 8 Initial states of outputs
; 10 BRIGHT period
; 12 DULL period
; 14 RESET

.equ SHARED_MEM, 0x40
.equ INIT_STATE, SHARED_MEM+8
.equ BRIGHT_PERIOD, SHARED_MEM+10
.equ DULL_PERIOD, SHARED_MEM+12
.equ PWM_RESET, SHARED_MEM+14

; Interface with PIO
.flag PWM_0,       P0.3            ; Port connected to LED_0(PIO3)
.flag PWM_1,       P0.4            ; Port connected to LED_1(PIO4)
.flag PWM_2,       P1.2            ; Port connected to LED_2(PIO10)
.flag PWM_3,       P1.3            ; Port connected to LED_3(PIO11)

; HW registers
.equ P0_DRIVE_EN, 0xc0
.equ P1_DRIVE_EN, 0xc8
.equ P2_DRIVE_EN, 0xd8
.equ P3_DRIVE_EN, 0xe8

; IDLE LOOP COUNT to make each step 4 microseconds
.equ IDLE_COUNT, 6

; R1 (LSB) and R2 (MSB) are used to count the number of pulses in a given phase

START:
    
; Set the stack up
    mov SP, #30H
    
; Adjust to the used PWM pins. 
; If needed apply only to required pins(P0.7,P1.1,P1.2,P1.3)
    ;mov P0_DRIVE_EN, #0x80
    ;mov P1_DRIVE_EN, #0x0E

    ; The drive enable register address is calculated from the port
    ; address. Ports 0 to 3 are at address 80, 90, a0 and b0
    ; respectively. The drive enable registers are at address c0, c8, d8
    ; and e8 respectively. To convert from the port address to the drive
    ; enable address, add 40h to the port address and subtract 8 if
    ; either of bits 4 or 5 are set.
           
    ; Each of the 8 bits in the drive enable register represents one of
    ; the I/O lines connected to that port. Which bit to set may be
    ; calculated from the port bit number.
    mov  (PWM_0 & 0f0h) + 40h - (((PWM_0 & 10h) >> 1) | ((PWM_0 & 20h) >> 2)), #((1 << (PWM_0 & 07h))|(1 << (PWM_1 & 07h)))
    mov  (PWM_3 & 0f0h) + 40h - (((PWM_3 & 10h) >> 1) | ((PWM_3 & 20h) >> 2)), #((1 << (PWM_2 & 07h))| (1 << (PWM_3 & 07h)))

;****************************************************************************
;   BRIGHT phase
;****************************************************************************

RESET:

    mov  R1, #0
    mov  R2, #0

BRIGHT_START:

;   Check if we should start this pulse or jump to dull phase
    mov  A, R1
    cjne A, BRIGHT_PERIOD, LSB_NE
    mov  A, R2
    cjne A, BRIGHT_PERIOD+1, MSB_NE
    mov  R1, #0
    mov  R2, #0
    ajmp DULL_START
   
MSB_NE:

    mov  A, R1
    
LSB_NE:

    ; A is R1
    add  A, #1
    mov  R1, A
    mov  A, R2
    addc A, #0
    mov  R2, A
    
START_PULSE:

    ; A is now the step number
    mov  A, #0
    mov  TEMP, INIT_STATE

BIT0:
    cjne A, SHARED_MEM, BIT1
    xrl  TEMP, #1
BIT1:
    cjne A, SHARED_MEM+1, BIT2
    xrl  TEMP, #2
BIT2:
    cjne A, SHARED_MEM+2, BIT3
    xrl  TEMP, #4
BIT3:
    cjne A, SHARED_MEM+3, DONE
    xrl  TEMP, #8
DONE:

; Output the resulted state
    lcall SET_PORTS

; Filling the rest of the step with a loop

    mov  R0, #0
IDLE:
    inc  R0
    cjne R0, #IDLE_COUNT, IDLE   ; Change loop count for corse step size adjustment

; Finer adjustment of step size. Add or remove NOPs below
    nop
;    nop
;    nop


; Next pulse after 255 steps     
    inc  A
    cjne A, #255, BIT0

; Even finer adjustment of pulse frequency can be done by adding NOPs here
;   nop

; Check RESET at the end of each pulse

    mov  A, PWM_RESET
    cjne A, #0, DO_RESET
    ajmp NO_RESET

DO_RESET:

    mov  PWM_RESET, #0
    ajmp RESET

NO_RESET:

    ajmp BRIGHT_START

;****************************************************************************
; DULL phase
;****************************************************************************

DULL_START:

;   Check if we should start this pulse

    mov  A, R1
    cjne A, DULL_PERIOD, LSB_NE2
    mov  A, R2
    cjne A, DULL_PERIOD+1, MSB_NE2
    ajmp RESET
    
MSB_NE2:

    mov  A, R1
    
LSB_NE2:

    ; A is R1
    add  A, #1
    mov  R1, A
    mov  A, R2
    addc A, #0
    mov  R2, A

START_PULSE2:

    ; A is now the step number
    mov  A, #0
    mov  TEMP, INIT_STATE
BIT0_2:
    cjne A, SHARED_MEM+4, BIT1_2
    xrl  TEMP, #1
BIT1_2:
    cjne A, SHARED_MEM+5, BIT2_2
    xrl  TEMP, #2
BIT2_2:
    cjne A, SHARED_MEM+6, BIT3_2
    xrl  TEMP, #4
BIT3_2:
    cjne A, SHARED_MEM+7, DONE2
    xrl  TEMP, #8
DONE2:

; Output the resulted state
    lcall SET_PORTS

; Filling the rest of the step
    mov  R0, #0
IDLE2:
    inc  R0
    cjne R0, #IDLE_COUNT, IDLE2   ; Change loop count for corse step size adjustment

; Finer adjustment of step size. Add or remove NOPs below
    nop
;    nop
;    nop

; Next pulse after 255 steps     
    inc    A
    cjne   A, #255, BIT0_2

; Even finer adjustment of pulse frequency can be done by adding NOPs here
;   nop

; Check RESET at the end of each pulse

    mov  A, PWM_RESET
    cjne A, #0, DO_RESET2
    ajmp NO_RESET2
    
DO_RESET2:

    mov  PWM_RESET, #0
    ajmp RESET

NO_RESET2:

    ajmp     DULL_START

;SET_PORTS sub-routine
SET_PORTS:
    ;Backup A
    mov R3, A

    ;Set ports
    mov A, TEMP
    rrc A
    mov PWM_0, C
    rrc A
    mov PWM_1, C
    rrc A
    mov PWM_2, C
    rrc A
    mov PWM_3, C

    ;Restore A
    mov A, R3
    ret
; end sub-routine

其中内存定义为:

.equ SHARED_MEM, 0x40
.equ INIT_STATE, SHARED_MEM+8
.equ BRIGHT_PERIOD, SHARED_MEM+10
.equ DULL_PERIOD, SHARED_MEM+12
.equ PWM_RESET, SHARED_MEM+14

PIO设置API为SET_PORTS,这里它采用了位操作的方式来配置PIO状态。


工程使用:

1.把工程源代码添加到实际的CSR101x项目中(编译需要用到CSR uEnergy SDK xIDE)。

2.修改其中的PIO为实际用的PIO,这有两个地方需要修改:

   a,fast_pwm.h中的PIO定义:

/* PIO numbers for PWM Ports */
#define PWM0_PORT  3
#define PWM1_PORT  4
#define PWM2_PORT  10
#define PWM3_PORT  11

   b.汇编代码中的PIO定义:

; Interface with PIO
.flag PWM_0,       P0.3            ; Port connected to LED_0(PIO3)
.flag PWM_1,       P0.4            ; Port connected to LED_1(PIO4)
.flag PWM_2,       P1.2            ; Port connected to LED_2(PIO10)
.flag PWM_3,       P1.3            ; Port connected to LED_3(PIO11)


源代码:Fast_PWM.rar

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值