事情是这样的:随着大家都在玩f1c100s,作为一直玩GUI的我也注意到了该芯片,性价比比较高适合驱屏,该芯片CPU内核为ARM9,刚好是我比较熟悉的核,自己写的GUI一直用ucos在树莓派上跑,ucos我玩了太多年了,也翻译过ucos的代码,毕竟这个os并不免费,只能作为学习用途。单纯的多线程OS玩了太久,最近萌生了要自己写个多进程多线程OS,于是在网上搜集整理相关资料,感谢前辈们无私奉献、辛苦踩坑,尤其是xboot这样的大佬,以及其他一系列的大佬(不一一点名),最终它来了:基于tiny200 r3(f1c200s)板子的bootloader固件+多进程多线程OS镜像+APP开发模板。
大家都知道OS这个玩意相当复杂,请允许我详细介绍玩它的步骤,对,就是玩它...玩它...玩它...,详细步骤如下:
1、用传统方法通过usb口烧写boot.bin到spiflash的0x0位置;
2、用传统方法通过usb口烧写loader.bin到spiflash的0x3000位置;
3、注意因为loader有串口下载功能,完成以上两步之后,就再也不需要从usb口下载了;
4、数据线连接tiny200 r3的串口,并连接到电脑,打开串口工具(XCOM),选择正确串口,设置波特率115200;
5、轻按tiny200的RST键,松开后,迅速通过串口工具发送字符'2'~'6'中的一个,这时进入下载APP模式;
6、使用串口工具的文件发送功能,选择APP0.bin,点击发送,发送完毕后,该APP就保存到了spiflash中;
7、重复4到6步可以下载不同的APP,分别选择字符'2'到'6'一共允许下载5个APP到spiflash中;
8、轻按tiny200的RST键,松开后,迅速通过串口工具发送字符'1',这时进入下载OS模式;
9、使用串口工具的文件发送功能,选择arm9os.bin,点击发送,发送完毕后,OS开始运行;
10、使用串口的多条发送功能,发送字符串"run app02",这时OS会加载第一个APP并运行它,如果发送字符串"run app03",这时OS加载第二个APP并运行它,以此类推......
注释:字符'2'~字符'6'是下载APP的指令,字符'1'是下载操作系统的指令,下载时不要选错文件;"run app"是加载并运行APP的指令。
相关文件如下:
串口工具:XCOM V2.0.exe
boot:boot.rar
loader:_loader.rar
arm9os:arm9os.rar
APP模板:APP0.rar
离线
注意先下载APP,再下载OS,每个APP下载一次即可,他们是保存在spiflash中的;OS是直接下载到SDRAM运行的,掉电就消失了。可以修改APP模板编译出不同的APP下载进去,串口观察APP运行情况。该OS有强制线程调度能力,以免用户忘记自主放弃CPU,其他线程不能运行;线程可以分别像写裸机程序一样,系统自动为他们并行运行,无感知并行。
离线
断电重启后需要重新下载OS吗,略麻烦
离线
断电重启后需要重新下载OS吗,略麻烦
你也可以用常规方法通过USB口将arm9os.bin下载到spiflash的0x8000处,这样就固化了。我自己是因为要一直更新OS,一直在调试OS,所以保存OS到spiflash的功能没打开,其实我的OS有个头,当其中某字段设为1(该字段决定OS自己是否被保存)时,loader发现这个1,就会保存OS到spiflash。头如下:
typedef struct {// 80字节
uint32_t magic; // 文件标记0x27190556
uint32_t struct_size; // 自身尺寸
uint32_t entry; // 程序入口
uint32_t kernel_addr; // header的加载地址
uint32_t kernel_size; // header的长度
uint32_t kernel_crc; // header的校验值
uint32_t bss_addr; // BSS地址
uint32_t bss_size; // BSS大小
uint32_t isr_addr; // 中断服务程序地址数组首地址
uint32_t isr_nbr; // 地址个数
uint32_t rsv1; // 保留(最低位为1表示程序加载到SDRAM之后还要烧到spi flash)
uint32_t rsv2; // 保留
uint8_t name[32]; // 字符串
} kernel_header_t;
最近编辑记录 三哥 (2022-04-21 12:05:51)
离线
@LinjieGuo
大佬你好!你的疑问1答案如下:
loader在运行os之前已为os部署好了虚拟环境,当然os在运行过程中也有部署虚拟环境的能力,各用户进程虚拟地址空间是严格分开的,loader中相关部署代码如下
uint32_t fill_table(void)
{
uint32_t* base_addr;
base_addr = (uint32_t*)PAGE_TABLE_ADDR;//第一个内核进程的页表地址//?物理地址,(至少16k对齐)
//4G空间全部归0(不可访问),页表项全0会产生错误
InitPageTable(base_addr);
// 起始向量表在此,第一阶段代码在此CNB,SRAM处物理和虚拟地址均可回收
map_l1_section(base_addr, 0x00000000, 0x00000000, 1 * SIZE_1M, PRI_RW_USR_RW, USE_CACHE);
// SDRAM空间,CB
map_l1_section(base_addr, 0xc0000000, 0x80000000, 16 * SIZE_1M, PRI_RW_USR_NOT, USE_CACHE|USE_BUFFER);
//任务SP、显示缓冲区放在用户空间
//内核堆栈、向量
map_l1_section(base_addr, 0xfff00000, 0x83f00000, 1 * SIZE_1M, PRI_RW_USR_NOT, USE_CACHE);
//页表
map_l1_section(base_addr, 0xffc00000, 0x83c00000, 3 * SIZE_1M, PRI_RW_USR_NOT, USE_CACHE);
map_l1_section(base_addr, 0xff900000, 0x01e00000, 1 * SIZE_1M, PRI_RW_USR_NOT, 0); // 外设
map_l1_section(base_addr, 0xff800000, 0x01c00000, 1 * SIZE_1M, PRI_RW_USR_NOT, 0); // 外设
return (uint32_t)base_addr;
/*
* 虚拟地址0xffc00000-0xfff00000 对应物理地址0x83c00000-0x83f00000 存放页表+内核栈、向量表(其中内核栈、向量表在最后一页)
*
*虚拟地址0xff800000-0xffb00000 分给外设使用
*
*虚拟地址0xc0000000开始分给内核代码+内核堆使用
*/
}
离线
@LinjieGuo
大佬的疑问2答案如下:
我的内核有进程/线程间通信模块,如:共享内存、管道、sem、mutex、SpinLock、SleepLock等等,只不过我现在没有以syscall方式提供出来,用户处在ARM的usr模式是不能调用的,因为启用了MMU并设置了访问权限,用户直接调用会导致异常,正常情况是要以syscall方式提供给用户使用的。
离线
@LinjieGuo
加载用户APP的详细准备工作代码如下:
void * LoadBin(char * buf, int vir_offset, char * file_start, process * pro)//vir_offset填0
{
void * stack;
app_hdr * phdr;
uint32_t vir_start;
int page;
CopyFile(buf, 0, file_start, 0, sizeof(app_hdr));
// spl_print32((u32)(file_start));spl_prints(" \r\n \r\n", 0);
// show_data((void*)buf);
phdr = (void*)buf;
vir_start = (phdr->load_addr) >> 20;
if(0x2e617070 != phdr->magic) return (void*)0xffffffff;
//准备虚拟空间
//需要适应phdr->load_addr - phdr->stack虚拟地址段
page = ((phdr->stack) >> 20) - (vir_start) + 1;//计算页数
while(page --)
{
PagePrepare(pro, vir_start << 20);
//拷贝映射关系到临时页表
*(Get_pgt_base(-1) + (int32_t)(vir_start)) = *(Get_pgt_base(pro->id_pro) + (int32_t)(vir_start));
vir_start ++;
}
//拷贝代码
vir_offset = 0;//这个使用0很有必要
CopyFile((char*)(phdr->load_addr), vir_offset, file_start, phdr->code_offset, phdr->code_size);
//清bss
FillData((char*)phdr->bss_addr, 0, phdr->bss_size);
//准备堆栈数据
stack = StackAlign((void*)(phdr->stack));
PrepareStack(stack, (void*)phdr->entry);
return stack;
}
离线