有了前几章的基础教程,我相信大家已经迫不及待的想编写main函数了,是的,完全可以这样,跟MCU有何差异呢,直接操作寄存器就可以了实现各种外设的开发工作了。但是、但是,我们是不是可以做的更好呢,STM32单片机为何能流行,只是因为性价比?我想更为关键的还是ST公司不遗余力的开发出一套可能方便快捷调用的函数库,开发效率高,周期短,完成度高,才是一款方案的真正竞争力。
好了,我们也需要将我们所常用的各种外设编写成可以方便调用的库函数。其实大家看过前面的教程就可以发现,有这样的趋势了,比如LED灯,蜂鸣器示例,都是先包装成方便调用接口了。回忆一下当时的定义:
LED灯的库函数头文件:
enum led_name {
LED_NAME_LED1 = 1,
LED_NAME_LED2 = 2,
LED_NAME_LED3 = 3,
LED_NAME_LED4 = 4,
};
enum led_status {
LED_STATUS_OFF = 0,
LED_STATUS_ON = 1,
};
void led_initial(void);
void led_set_status(enum led_name name, enum led_status status);
蜂鸣器的库函数头文件:
enum beep_status {
BEEP_STATUS_OFF = 0,
BEEP_STATUS_ON = 1,
};
void beep_initial(void);
void beep_set_status(enum beep_status status);
恩,这看起来,都是些比较简单的外设驱动包装,比如再复杂点呢,像时钟,中断,串口,液晶屏显示这些,也可以这样么,回答当然是肯定的,先从时钟管理开始,因为是裸机,我们包装也不需太过高层次,比如支持调用参考计数,支持父子关系,像内核时钟管理一样强大,就做过了,简洁好用,才是王道。
其实,时钟管理,本质上是要能提供一个方便调用的接口函数,给各种外设驱动调用,比如串口,需要设置波特率,那我们不可能使用硬编码那种十分不灵活的方式,我们完全可以写出一个公式,自动进行各种波特率运算,代码可读性强,可移植性高。
首先,我们时钟管理只需提供一个获取某个时钟的频率即可,只读方式,不提供设置功能,这样,我们的问题就简化很多,定义如下:
bool_t clk_get_rate(const char * name, u64_t * rate);
那我们如果实现这个函数呢,看代码:
struct clk_t
{
/* The clk name */
const char * name;
/* The clk's rate, HZ */
u64_t rate;
};
bool_t clk_get_rate(const char * name, u64_t * rate)
{
int i;
for(i=0; i< ARRAY_SIZE(exynos4412_clocks); i++)
{
if(strcmp(exynos4412_clocks[i].name, name) == 0)
{
if(rate)
*rate = exynos4412_clocks[i].rate;
return TRUE;
}
}
return FALSE;
}
是从一个数组里匹配相应的时钟名字,然后返回该数组里标注的时钟值。那这个数组又是怎么来的呢
static u64_t exynos4412_get_pll_clk(u64_t fin, enum EXYNOS4412_PLL pll)
{
u32_t r, k, m, p, s;
u64_t fout;
switch(pll)
{
/*
* FOUT = MDIV * FIN / (PDIV * 2^(SDIV))
*/
case EXYNOS4412_APLL:
r = readl(EXYNOS4412_APLL_CON0);
m = (r >> 16) & 0x3ff;
p = (r >> 8) & 0x3f;
s = r & 0x7;
fout = m * (fin / (p * (1 << s)));
break;
/*
* FOUT = MDIV * FIN / (PDIV * 2^(SDIV))
*/
case EXYNOS4412_MPLL:
r = readl(EXYNOS4412_MPLL_CON0);
m = (r >> 16) & 0x3ff;
p = (r >> 8) & 0x3f;
s = r & 0x7;
fout = m * (fin / (p * (1 << s)));
break;
/*
* FOUT = (MDIV + K / 65536) * FIN / (PDIV * 2^SDIV)
*/
case EXYNOS4412_EPLL:
r = readl(EXYNOS4412_EPLL_CON0);
k = readl(EXYNOS4412_EPLL_CON1);
m = (r >> 16) & 0x1ff;
p = (r >> 8) & 0x3f;
s = r & 0x7;
k = k & 0xffff;
fout = (m + k / 65536) * (fin / (p * (1 << s)));
break;
/*
* FOUT = (MDIV + K / 65535) * FIN / (PDIV * 2^SDIV)
*/
case EXYNOS4412_VPLL:
r = readl(EXYNOS4412_VPLL_CON0);
k = readl(EXYNOS4412_VPLL_CON1);
m = (r >> 16) & 0x1ff;
p = (r >> 8) & 0x3f;
s = r & 0x7;
k = k & 0xffff;
fout = (m + k / 65535) * (fin / (p * (1 << s)));
break;
default:
return 0;
}
return fout;
}
static u64_t exynos4412_get_mout_apll(u64_t fin)
{
u32_t reg;
u64_t ret;
reg = readl(EXYNOS4412_CLK_SRC_CPU);
if( ((reg >> 0) & 0x1) == 0x1 )
ret = exynos4412_get_pll_clk(fin, EXYNOS4412_APLL);
else
ret = fin;
return ret;
}
static u64_t exynos4412_get_sclk_apll(u64_t fin)
{
u32_t reg;
u64_t ret, mout_apll;
mout_apll = exynos4412_get_mout_apll(fin);
reg = (readl(EXYNOS4412_CLK_DIV_CPU0) >> 24) & 0x7;
ret = mout_apll / (reg + 1);
return ret;
}
static u64_t exynos4412_get_mout_mpll(u64_t fin)
{
u32_t reg;
u64_t ret;
reg = readl(EXYNOS4412_CLK_SRC_CPU);
if( ((reg >> 24) & 0x1) == 0x1 )
ret = exynos4412_get_pll_clk(fin, EXYNOS4412_MPLL);
else
ret = fin;
return ret;
}
static u64_t exynos4412_get_sclk_mpll(u64_t fin)
{
return exynos4412_get_mout_mpll(fin);
}
static u64_t exynos4412_get_sclk_mpll_user_c(u64_t fin)
{
return exynos4412_get_sclk_mpll(fin);
}
static u64_t exynos4412_get_sclk_mpll_user_t(u64_t fin)
{
return exynos4412_get_sclk_mpll(fin);
}
static u64_t exynos4412_get_mout_core(u64_t fin)
{
u32_t reg;
u64_t ret;
reg = readl(EXYNOS4412_CLK_SRC_CPU);
if( ((reg >> 16) & 0x1) == 0x1 )
ret = exynos4412_get_sclk_mpll_user_c(fin);
else
ret = exynos4412_get_mout_apll(fin);
return ret;
}
static u64_t exynos4412_get_sclk_epll(u64_t fin)
{
u32_t reg;
u64_t ret;
reg = readl(EXYNOS4412_CLK_SRC_TOP0);
if( ((reg >> 4) & 0x1) == 0x1 )
ret = exynos4412_get_pll_clk(fin, EXYNOS4412_EPLL);
else
ret = fin;
return ret;
}
static u64_t exynos4412_get_sclk_vpll(u64_t fin)
{
u32_t reg;
u64_t ret;
reg = readl(EXYNOS4412_CLK_SRC_TOP0);
if( ((reg >> 8) & 0x1) == 0x1 )
ret = exynos4412_get_pll_clk(fin, EXYNOS4412_VPLL);
else
ret = fin;
return ret;
}
static u64_t exynos4412_get_armclk(u64_t fin)
{
u32_t reg, core, core2;
u64_t ret, mout_core;
mout_core = exynos4412_get_mout_core(fin);
reg = readl(EXYNOS4412_CLK_DIV_CPU0);
core = (reg >> 0) & 0x7;
core2 = (reg >> 28) & 0x7;
ret = mout_core / (core + 1) / (core2 + 1);
return ret;
}
static u64_t exynos4412_get_sclk_uart(u64_t fin, u32_t ch)
{
u32_t reg;
u64_t ret, mout_uart;
if(ch < 0 || ch > 4)
return 0;
reg = (readl(EXYNOS4412_CLK_SRC_PERIL0) >> (ch * 4)) & 0xf;
switch(reg)
{
case 0x6:
mout_uart = exynos4412_get_sclk_mpll_user_t(fin);
break;
case 0x7:
mout_uart = exynos4412_get_sclk_epll(fin);
break;
case 0x8:
mout_uart = exynos4412_get_sclk_vpll(fin);
break;
default:
mout_uart = fin;
break;
}
reg = (readl(EXYNOS4412_CLK_DIV_PERIL0) >> (ch * 4)) & 0xf;
ret = mout_uart / (reg + 1);
return ret;
}
/*
* the array of clocks, which will to be setup.
*/
static struct clk_t exynos4412_clocks[13];
/*
* setup the exynos4412's clock array.
*/
static void exynos4412_setup_clocks(u64_t xtal)
{
exynos4412_clocks[0].name = "xtal";
exynos4412_clocks[0].rate = xtal;
exynos4412_clocks[1].name = "sclk_apll";
exynos4412_clocks[1].rate = exynos4412_get_sclk_apll(xtal);
exynos4412_clocks[2].name = "sclk_mpll";
exynos4412_clocks[2].rate = exynos4412_get_sclk_mpll(xtal);
exynos4412_clocks[3].name = "sclk_epll";
exynos4412_clocks[3].rate = exynos4412_get_sclk_epll(xtal);
exynos4412_clocks[4].name = "sclk_vpll";
exynos4412_clocks[4].rate = exynos4412_get_sclk_vpll(xtal);
exynos4412_clocks[5].name = "armclk";
exynos4412_clocks[5].rate = exynos4412_get_armclk(xtal);
exynos4412_clocks[6].name = "sclk_uart0";
exynos4412_clocks[6].rate = exynos4412_get_sclk_uart(xtal, 0);
exynos4412_clocks[7].name = "sclk_uart1";
exynos4412_clocks[7].rate = exynos4412_get_sclk_uart(xtal, 1);
exynos4412_clocks[8].name = "sclk_uart2";
exynos4412_clocks[8].rate = exynos4412_get_sclk_uart(xtal, 2);
exynos4412_clocks[9].name = "sclk_uart3";
exynos4412_clocks[9].rate = exynos4412_get_sclk_uart(xtal, 3);
exynos4412_clocks[10].name = "sclk_uart4";
exynos4412_clocks[10].rate = exynos4412_get_sclk_uart(xtal, 4);
exynos4412_clocks[11].name = "fimd";
exynos4412_clocks[11].rate = 200 * 1000 * 1000;
exynos4412_clocks[12].name = "pclk";
exynos4412_clocks[12].rate = 100 * 1000 * 1000;
}
根据当前寄存器的配置,将相应的时钟名及时钟值直接赋值到查询数组,时钟值就是手册可以写出运算公式,直接换算出来结果,后期调用时,无需再次计算。
举个示例,LCD驱动里就需要获取当前的fimd时钟,然后根据fimd时钟以及屏参,计算出液晶屏的像素时钟,并设置相应寄存器。
static bool_t exynos4412_fb_set_clock(struct exynos4412_fb_data_t * dat)
{
u64_t fimd, pixel_clock;
u32_t div;
u32_t cfg;
if(!clk_get_rate("fimd", &fimd))
return FALSE;
pixel_clock = ( dat->freq * (dat->timing.h_fp + dat->timing.h_bp + dat->timing.h_sw + dat->width) *
(dat->timing.v_fp + dat->timing.v_bp + dat->timing.v_sw + dat->height) );
div = (u32_t)(fimd / pixel_clock);
if((fimd % pixel_clock) > 0)
div++;
cfg = readl(dat->regbase + VIDCON0);
cfg &= ~( (1 << 16) | (1 << 5) );
cfg |= VIDCON0_CLKVAL_F(div - 1);
writel(dat->regbase + VIDCON0, cfg);
return TRUE;
}
本次教程就讲到这里,下一个教程,我们需要看看如何实现中断管理,貌似这个麻烦了点。有疑问的网友可以直接留言,或者加QQ8192542,欢迎探讨。