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