芯片全局配置(CHIPCTRL)
电源管理模块的基地址0xbfeb0000,寄存器定义可见用户手册第7-8页。其定义在./include/ls1x.h
中。
#define PMU_BASE PHYS_TO_UNCACHED(0x1feb0000)
...
#define PMU_CHIPCTRL *(volatile unsigned int *)(PMU_BASE+0x00)
#define PMU_CMDSTS *(volatile unsigned int *)(PMU_BASE+0x04)
#define PMU_COUNT *(volatile unsigned int *)(PMU_BASE+0x08)
#define PMU_COMPARE *(volatile unsigned int *)(PMU_BASE+0x0c)
...
芯片上电初始化过程中首先就需要配置全局时钟,1c102的时钟结构可以在数据手册中找到:
我们建议32K时钟尽量使用外部时钟,内部32K时钟作为外部时钟的备份有较大误差,仅供系统运行,不能保证精度。
8M时钟的选择则根据个人需求来,一般我们选择内部32M晶振做分频使用,这样可以有一个11M或32M主频供CPU使用。
程序上,初始化全局时钟调用SystemClockInit()
函数:
uint32_t SystemClockInit(void)
{
CLOCK_InitTypeDef CLOCK_InitStruct = {0};
CLOCK_StructInit(&CLOCK_InitStruct);
#if defined (LS1C102)
CLOCK_InitStruct.OscillatorType = CLOCK_OSCILLATORTYPE_HSI | CLOCK_OSCILLATORTYPE_HSE | CLOCK_OSCILLATORTYPE_LSI | CLOCK_OSCILLATORTYPE_LSE;
CLOCK_InitStruct.HSEState = CLOCK_HSE_OFF;
CLOCK_InitStruct.LSEState = CLOCK_LSE_ON;
CLOCK_InitStruct.LSISpeed = CLOCK_LSI_SPEED_32K;
CLOCK_InitStruct.StartClkHS = CLOCK_START_CLK_HS_5140US;
CLOCK_InitStruct.FastEn = CLOCK_FASTEN_ON;
CLOCK_InitStruct.TurboEn = CLOCK_TURBO_ON;
#elif defined (LS1C103)
...
#endif
if(CLOCK_Init(&CLOCK_InitStruct) != SUCCESS)
{
return -1;
}
return 0;
}
其主要设置了几种需要用户选择的时钟设置,时钟配置使用到的结构体CLOCK_InitTypeDef
中成员与用户手册中参数的映射关系:
typedef struct
{
uint32_t OscillatorType;
uint32_t HSEState;
uint32_t LSEState;
uint32_t LSISpeed;
uint32_t StartClkHS;
uint32_t FastEn;
uint32_t TurboEn;
}CLOCK_InitTypeDef;
调用CLOCK_StructInit()
函数配置完成后调用CLOCK_Init()
完成对时钟的配置。
ls1x_clock.h
中还定义了几个辅助函数,如:
CLOCK_GetClocksFreq()
获取系统时钟频率。
CLOCK_GetFlagStatus
获取当前时钟状态。
CHIPCTRL
还对ADC控制器做了一些补充功能定义在此处,比如说ADC通道的电源控制、ADC0的上下拉和数字输入使能等。通过PMU_CHIPCTRL直接配置即可,不再赘述。
IO功能复用
配置完时钟后,我们一般会对102上的引脚功能进行定义,除却VCC与GND外,102的功能引脚分为“主功能”、“GPIO”、“第一复用”和“第二复用”四个功能,MCU默认工作在“GPIO”模式下,如果需要更改引脚功能就需要学习以下方法。
102引脚复用表可以在102的数据手册中找到。例如下图:
102共有64个复用GPIO,它们被分为4组,每组16个GPIO,在示例代码中被称为AFIOx。查阅用户手册可知由IOSEL
位控制IO的复用情况。
对IO功能进行配置,接口为GPIOInit()
。该函数默认配置了所有IO为GPIO模式,调用AFIO_RemapConfig()
可配置某一位的IO模式。
直接调用AFIO_RemapConfig()
实在是太麻烦了,还需要对GPIO进行换算,重写了一个接口可以通过设置IO号(0~63)和模式(可查看GPIOFunc_TypeDef定义)来设置某一IO功能:
void remap_gpio(unsigned char gpio_NO, GPIOFunc_TypeDef gpio_fun)
{
switch(gpio_NO/16)
{
case 0:
AFIO_RemapConfig(AFIOA, 0x1<<(gpio_NO%16), gpio_fun);
break;
case 1:
AFIO_RemapConfig(AFIOB, 0x1<<(gpio_NO%16), gpio_fun);
break;
case 2:
AFIO_RemapConfig(AFIOC, 0x1<<(gpio_NO%16), gpio_fun);
break;
case 3:
AFIO_RemapConfig(AFIOD, 0x1<<(gpio_NO%16), gpio_fun);
break;
default:
break;
}
}
另外对于GPIO值的设置,102提供了两种方法:
- 通过GPIOA_O或者GPIOB_O直接设置输出电平(设置模式的时候是四组32位寄存器,这里是两组32位寄存器,不要搞错了)。
- 通过GPIOBIT位访问端口直接设置GPIO方向和电平,我个人比较推荐这种方式。
下面给出一些功能函数:
static void set_gpioa_status(unsigned char num, PIN_STATUS level)
{
if(level)
{
PMU_GPIOA_O = PMU_GPIOA_O | (1 << num);
}else{
PMU_GPIOA_O = PMU_GPIOA_O & (~(1 << num));
}
}
static void set_gpiob_status(unsigned char num, PIN_STATUS level)
{
if(level)
{
PMU_GPIOB_O = PMU_GPIOB_O | (1 << num);
}else{
PMU_GPIOB_O = PMU_GPIOB_O & (~(1 << num));
}
}
void set_gpio_value(unsigned char gpio_NO, PIN_STATUS level)
{
switch(gpio_NO/32)
{
case 0:
set_gpioa_status(gpio_NO%32, level);
break;
case 1:
set_gpiob_status(gpio_NO%32, level);
break;
default:
break;
}
}
void Set_gpio_value(unsigned char GPIOx, unsigned char mode, unsigned char value)
{
unsigned char data = 0;
assert_param(IS_GPIO_PIN(GPIOx));
if(mode)
{
data = (mode<<1) | value;
PMU_GPIOBit(GPIOx) = data;
}
return;
}
- 关于GPIO中断的设置,请参考PMU部分
EXINTEN
、EXINTPOL
、EXINTEDGE
和EXINTSRC
寄存器。接口如下:
void set_gpio_irq(unsigned char gpio_NO, EXTIMode_TypeDef gpio_mod, EXTITrigger_TypeDef gpio_trigger, FunctionalState statu)
{
EXTI_InitTypeDef EXTI_InitStruct;
EXTI_InitStruct.EXTI_Mode = gpio_mod;
EXTI_InitStruct.EXTI_Trigger = gpio_trigger;
EXTI_InitStruct.EXTI_GpioCmd = statu;
if(gpio_NO >= 0 && gpio_NO <= 7)
{
EXTI_InitStruct.EXTI_GPIO = (unsigned int)(0x1 << (gpio_NO));
}else if(gpio_NO >= 16 && gpio_NO <= 23)
{
EXTI_InitStruct.EXTI_GPIO = (unsigned int)(0x1 << (gpio_NO-16+8));
}else if(gpio_NO >= 32 && gpio_NO <= 39)
{
EXTI_InitStruct.EXTI_GPIO = (unsigned int)(0x1 << (gpio_NO-32+16));
}else if(gpio_NO >= 48 && gpio_NO <= 55)
{
EXTI_InitStruct.EXTI_GPIO = (unsigned int)(0x1 << (gpio_NO-48+24));
}else{
return;
}
EXTI_Init(EXTI, &EXTI_InitStruct);
PMU_CMDSTS |= 0x02000000;
}
set_gpio_irq(GPIO_32, EXTI_Mode_Edge, EXTI_Trigger_Rising_High, DISABLE);
定时器
102实现了与HPET类似的1路定时器,拥有单次触发和周期触发两种方式。其包括count、compare、step三个寄存器,count为计数值,compare为比较值,step为周期触发的步进值。
该部分功能定义在./private/ls1c102/ls1c102_ptimer.h
中,示例代码为1c102_cmd.c
中int hpet(int argc, void *argv[])
,可以看一下这个函数实现:
int hpet(int argc, void *argv[])
{
printf("\r\n hpet timer start....\n");
uint32_t periodic,cmp,stp;
if(argc < 4)
{
TIM_Cmd(0);
TIM_ITConfig(0);
printf("\r\n Usage: hpet <periodic> <start_ms> <sec>");
return 1;
}
periodic = str2num(argv[1]);
cmp = str2num(argv[2]);
stp = str2num(argv[3]);
TIM_InitTypeDef TIM_InitStruct;
TIM_StructInit(&TIM_InitStruct);
TIM_InitStruct.TIME_PERIODIC = periodic << 2;
TIM_InitStruct.TIME_CMP = cmp * 7999;
TIM_InitStruct.TIME_STP = stp * 8000000;
TIM_Init(&TIM_InitStruct);
return 0;
}
TIM_Cmd()
控制定时器启停,TIM_ITConfig()
控制定时器中断使能。
TIM_InitTypeDef是定时器定义结构,结构为:
typedef struct
{
uint32_t Clock_Freq;
uint32_t TIME_CMP;
uint32_t TIME_STP;
uint32_t TIME_CNT;
uint32_t TIME_PERIODIC;
uint32_t TIME_INTEN;
uint32_t TIME_START;
} TIM_InitTypeDef;
成员分别为时钟频率,比较寄存器、步进寄存器、计数寄存器,后面是对配置寄存器几个参数的实现,可以在用户手册找到对应定义。
TIM_StructInit()
结构体初始化函数,TIM_Init()
定时器初始化函数。
因为102只有一个定时器,且没有前置分频器调整时钟频率,在使用上来说是不方便的。因此可以设计一个简单架构通过中断复用该定时器,从而通过牺牲一部分性能来达到执行多个不同定时触发任务的效果。
#define TIMER_NUM 100
typedef struct{
unsigned char que_number;
unsigned char status;
unsigned int execute_time;
int (*handle_fun)(void);
}Timertask_Typedef;
Timertask_Typedef mytimer_que[MYTIME_QUEUE_NUMBER]={0};
char Reg_timer_task(unsigned int exe_time, int (*handle_fun)(void))
{
unsigned char i = 0;
for(i=0; i<=MYTIME_QUEUE_NUMBER-1; ++i)
{
if(!mytimer_que[i].status){
mytimer_que[i].que_number = i;
mytimer_que[i].status = MYTIME_TASK_ENABLE;
mytimer_que[i].execute_time = exe_time;
mytimer_que[i].handle_fun = handle_fun;
break;
}
else {
if (i == MYTIME_QUEUE_NUMBER) {
printf("The queue is full.\n");
return -1;
}
}
}
return mytimer_que[i].que_number;
}
void Del_timer_task(unsigned char que_num)
{
mytimer_que[que_num].status = MYTIME_TASK_DISABLE;
return;
}
void Timer_IRQ_Handle()
{
unsigned char i = 0;
for(i=0; i<=MYTIME_QUEUE_NUMBER-1; ++i)
{
if(mytimer_que[i].status == MYTIME_TASK_ENABLE)
{
if(--mytimer_que[i].execute_time <= 0)
{
mytimer_que[i].handle_fun();
Del_timer_task(mytimer_que[i].que_number);
}
}
}
return;
}
unsigned int SystemFreq;
static void timer_start()
{
TIM_InitTypeDef timer_info;
TIM_StructInit(&timer_info);
timer_info.Clock_Freq = SystemFreq;
timer_info.TIME_CMP = ((timer_info.Clock_Freq*TIMER_NUM)/1000);
timer_info.TIME_STP = ((timer_info.Clock_Freq*TIMER_NUM)/1000);
TIM_Init(&timer_info);
return;
}
void Timer_Init()
{
int i = 0;
for(i=0; i<MYTIME_QUEUE_NUMBER-1; ++i)
{
mytimer_que[i].status = MYTIME_TASK_DISABLE;
mytimer_que[i].que_number = 0;
mytimer_que[i].execute_time = 0;
mytimer_que[i].handle_fun = NULL;
}
timer_start();
return;
}
通过让定时器以一个最小粒度来周期触发中断,将延时转变为触发中断的次数,并维护一个队列进行管理,上例的实现比较简单,执行任务的时候会产生阻塞。如果要确保正常运行的话可以通过保证任务之间的时间间隔或者调整任务执行为非阻塞架构即可。