今年以来我国IT厂商都在A lot的底层平台建设方面可谓是捷报频传,在操作系统方面有如像腾讯的Tiny OS、阿里的Ali Things OS3.0纷纷进行了开源;在数据库方面国产的时序库TdEngine在开源后也在Github上占据榜首位很久,在芯片方面阿里平头哥半导体公司也有很多大手笔,先是在今年7月底发布的玄铁910处理器,这款CPU基于RISC-V的基础架构,有16个core,使用12nm制程工艺,最高主频2.5GHZ,功耗仅0.2瓦,超越行业主流性能30%。
而在今年刚刚结束的世界互联网大会上,平头哥又现场宣布推出开源的MCU平台:无剑100 Open,包含了玄铁处理器、基础接口、开发环境,OS等。无剑芯片平台希望能把整个芯片的研发周期缩短50%,把开发成本降低50%,被阿里定位为面向下一代的AIoT产品的平台。
什么是MCU
MCU(Micro controller Unit)暨微控制单元,又称单片微型计算机(Single Chip Microcomputer )或者单片机,是把CPU、内存、定时器以及主板的大部分接口及总线功能(USB、A/D转换DMA)全部整合在一块芯片上,形成芯片级的计算机,以降低总体成本及功耗,可以在不同的应用场合做不同组合控制。在各类遥控器、汽车、步进马达、机器手等lot设备上,都可见到MCU的身影。
MCU之所以特别适合Alot其主要原因在于多数物联网终端对于性能都没有太高要求,整合相应外设模块,不但节约成本功耗,也不会产生散热问题,特别适合使用all in one理念进行设计。
比如本次开源的MCU其CPU、内存及主要外设的规格如下:
CPU 部分
代码解读平头哥的MCU
笔者发现无剑100( Github地址是https://github.com/T-head-Semi/wujian100_open),
开源的这一周时间里,我国各大技术社区都只见报道,却没有技术类文章对其源代码进行解读。由此可见全国各界都在在大力呼吁国产芯片跨跃式发展,但是其实整个行业对于芯片方面的知识储备却是不太充足。当我们真的有了开源的芯片却没有技术社区对其进行解读,未免是件憾事。
不过要想完整了解MCU芯片的代码,需要具备硬件电路设计、驱动程序及操作系统内核等方方面面的知识,这里笔者仅选取一个侧面带大家来看看这款MCU的定时器是如何工作的。
从整体上看计时器或者其它硬件与操作系统的关系如下图:
可以看到用户调用操作系统的服务,而系统服务再调用驱动,驱动再对相应硬件进行操作,以完成功能,哪怕是一个小小的计时器,如果想了解全貌也是相当的不易。不过好在阿里本次开源的比较彻底,上述代码也是一应俱全,下面我们依次展开来为大家解读。
硬件逻辑部分
无剑100平台的硬件Verilog描述源码都在https://github.com/T-head-Semi/wujian100_open/blob/master/soc目录下。
简单介绍一下Verilog语言,Verilog是一种硬件描述语言,用于从算法级、门级到开关级的多种抽象设计层次的数字系统建模。被建模的数字系统对象的复杂性可以介于简单的门和完整的电子数字系统之间。数字系统能够按层次描述,并可在相同描述中显式地进行时序建模。
可以看出Verilog是一种专门用于硬件描述的语言,有一定的编程知识及数字电路基础学习起来并不麻烦,本次要介绍的计时器的硬件源码在https://github.com/T-head-Semi/wujian100_open/blob/master/soc/tim.v,节选一部分代码对大家进行解读:
always@(*)/*Verilog的逻辑一般由alway关键字定义,其中()内部的变量变化时执行下面代码,如果是*则代表所有input发生变化都会解发语句执行*/
begin
if((psel == 1'b1) && (penable == 1'b1) && (pwrite == 1'b1)) begin/*verilog中的如1'b1,'前面的1代表一位长度,b代表二进制编码,'后面的1代表具体数值,本例中是说psel即选中标志,与penable即可用标志和pwirte即可写标志均为1是进入以下语句*/
if(paddr[TIMER_ADDR_LHS:2] == TIMER1LC_OFFSET)//如果是timer1的LOADCOUNT的OFFSET
timer1loadcount_wen = 1'b1;//将timer1的loadcount的event位配置为1
else
timer1loadcount_wen = 1'b0; //将timer1的loadcount的event位配置为0
end
else
timer1loadcount_wen = 1'b0; //将timer1的loadcount的event位配置为0
end
内存映射表如下:
名称 |
地址偏移 |
宽度 |
访问模式 |
复位数值 |
Description |
Timer1LoadCount |
0x00 |
32 |
R/W |
32’b0 |
将被装载入Timer1的value |
Timer1CurrentValue |
0x04 |
32 |
R |
32’b0 |
Timer1现在的value |
Timer1Control Reg |
0x08 |
4 |
R/W |
4’b0 |
Timer1的控制器 |
Timer1_int_clr |
0x0C |
1 |
R |
1’b0 |
Timer1清中断标志 |
Timer1Int Status |
0x10 |
1 |
R |
1’b0 |
Timer1中断状态 |
Timer2LoadCount |
0x14 |
32 |
R/W |
32’b0 |
将被装载入Timer1的value |
Timer2CurrentValue |
0x18 |
32 |
R |
32’b0 |
Timer1现在的value |
Timer2Control Reg |
0x1c |
4 |
R/W |
4’b0 |
Timer1的控制器 |
Timer2_int_clr |
0x20 |
1 |
R |
1’b0 |
Timer1清中断标志 |
Timer2Int Status |
0x24 |
1 |
R |
1’b0 |
Timer1中断状态 |
驱动部分
无剑100平台的硬件驱动代表均在以下目录https://github.com/T-head-Semi/wujian100_open/blob/master/sdk/csi_driver/wujian100_open/,驱动层一般是既有汇编又有C代码了,我们还是仅关注计时器的驱动,具体如下:https://github.com/T-head-Semi/wujian100_open/blob/master/sdk/csi_driver/wujian100_open/dw_timer.c
其中计时器初始化的函数如下:
timer_handle_t csi_timer_initialize(int32_t idx, timer_event_cb_t cb_event)//idx代表计时器的id,cb_event是回调事件
{
if (idx < 0 || idx >= CONFIG_TIMER_NUM) {
return NULL;
}
uint32_t base = 0u;
uint32_t irq = 0u;
void *handler;
int32_t real_idx = target_get_timer(idx, &base, &irq, &handler);
if (real_idx != idx) {//合法怕校验
return NULL;
}
dw_timer_priv_t *timer_priv = &timer_instance[idx];//设计timer的优先级
timer_priv->base = base;
timer_priv->irq = irq;//设置硬中断请求优先级
timer_priv->idx = idx;
dw_timer_reg_t *addr = (dw_timer_reg_t *)(timer_priv->base);
timer_priv->timeout = DW_TIMER_INIT_DEFAULT_VALUE;
#ifdef CONFIG_LPM
csi_timer_power_control(timer_priv, DRV_POWER_FULL);
#endif
timer_deactive_control(addr);//启用前先禁用
timer_priv->cb_event = cb_event;
if (cb_event != NULL) {
drv_irq_register(timer_priv->irq, handler);//注册硬件优先级
drv_irq_enable(timer_priv->irq);//启用
}
return (timer_handle_t)timer_priv;
}
那么这个驱动又为库文件https://github.com/T-head-Semi/wujian100_open/blob/master/sdk/libs/libc/clock_gettime.c提供服务,其中clock_timer_init函数会调用csi_timer_initialize函数初始化计时器。具体代码如下:
int clock_timer_init(void)
{
if (CLOCK_GETTIME_USE_TIMER_ID > CONFIG_TIMER_NUM) {
return EPERM;
}
uint32_t timer_loadtimer;
timer_handle = csi_timer_initialize(CLOCK_GETTIME_USE_TIMER_ID, timer_cb_fun);
if (timer_handle == NULL) {
return -1;
}
APB_FREQ = drv_get_timer_freq(CLOCK_GETTIME_USE_TIMER_ID);
timer_loadtimer = 10 * MILLION; /*10Mus=10s */
TIMER_LOADCOUNT = timer_loadtimer * (APB_FREQ / MILLION);
int ret = csi_timer_config(timer_handle, TIMER_MODE_RELOAD);
if (ret != 0) {
return -1;
}
ret = csi_timer_set_timeout(timer_handle, timer_loadtimer);
if (ret != 0) {
return -1;
}
unsigned int cv1, cv2;
csi_timer_get_current_value(timer_handle, &cv1);
csi_timer_get_current_value(timer_handle, &cv2);
if (cv2 > cv1) {
timer_count_rise = 1;
}
return 0;
}
内核层面的计时器
由于在硬件计时器数量有限,为避免浪费内核层面一般都使用双链表的方法,为各用户提供软计时器服务。这方面我在之前的文章中介绍过鸿蒙Lite OS的相关机制详见(https://blog.csdn.net/BEYONDMA/article/details/100049796),无剑100内核层面的软计时器与之比较类似,这里就不再赘述了。
以上就是笔者对于无剑100MCU计时器代码进行的解读,当然由于芯片层面涉及的知识层面太广,而笔者的水平有限难免有所纰漏,不过总算代表技术社区补上代码解读缺失这方面的遗憾了,也算是达成了心愿。
最后对于阿里平头哥将芯片平台开源,反哺技术社区的做法致以敬意。祝我国在这个物联网时代,能在操作系统、数据库和芯片等方面都能做出领先世界的产品。