stm32使用系统IO
系统 IO 和标准 IO
系统 IO 一般指的是 Linux/Unix 系统调用中关于 I/O 操作的统称,其中包括 open、read、write、close 等操作。
与系统 IO 对应还有标准 IO,标准 IO 是 ISO 标准中 C 语言标准定义的 IO 访问接口,例如 fprintf/fgets 等 C 语言标准中定义的文件访问接口。
在 Linux 系统中 open/read/write 等函数的底层实现是通过系统调用访问的,在 STM32 的裸机中没有操作系统,更没有这些系统调用。
但是我们可以用一种其他的方式去实现这些系统 IO,而不需要操作系统。
半主机模式重写文件访问接口
这个方法其实就是利用半主机模式,去重写系统库中关于半主机接口中关于文件访问接口的底层 **”弱定义”**。
这个听上去好像听陌生的,其实很多人都使用过,就是最简单的 printf 重定向。
在 GCC 重定向 printf 到串口使用了如下代码:
int _write(int fd, char * ptr, int len)
{
HAL_UART_Transmit(&huart1, (uint8_t *) ptr, len, HAL_MAX_DELAY);
return len;
}
这个就是在半主机模式下重写了 write 函数的底层接口,当系统调用 printf 函数时最终会调到 _write 函数向串口写入数据。
在 ARM 关于主机模式的文档中,Direct semihosting C library function dependencies 一节提供了可重写的系统 IO 的底层函数。
通过重写上述列表中的函数,即可通过调用 C 库 系统 IO 访问。
构建文件系统
在上面介绍使用系统 IO 的基本原理:通过重写 _open/_write/_read 等接口,即可通过 open/write/read 接口访问。
但是以上只提供了一系列系统接口,并将其与标准 IO 绑定,可以使用 open/fopen 等函数进行访问,但是具体访问的数据依旧需要自己进行实现。
在这次测试中我选用了 LittleFS 作为文件系统,使用 RAM 中预分配的全局变量作为存储介质,构建了一个基于内存的文件系统。(开发板没有 Flash 先用 RAM 代替了。。。)
其 _open 函数如下:
// 文件描述符列表,不包括标准输入输出, 最大 fd 为 FS_FILE_MAX + 3
lfs_file_t *g_file_list[FS_FILE_MAX] = {0};
int _open(const char *name, int flags)
{
int i;
int i_flags = 0;
if ((flags & O_CREAT) == O_CREAT) i_flags |= LFS_O_CREAT;
if ((flags & O_RDONLY) == O_RDONLY) i_flags |= LFS_O_RDONLY;
if ((flags & O_WRONLY) == O_WRONLY) i_flags |= LFS_O_WRONLY;
if ((flags & O_RDWR) == O_RDWR) i_flags |= LFS_O_RDWR;
for (i = 0; i < FS_FILE_MAX; i++)
{
if (g_file_list[i] == NULL)
{
g_file_list[i] = malloc(sizeof(lfs_file_t));
lfs_file_open(&g_lfs, g_file_list[i], name, i_flags);
return i + 3;
}
}
return -1;
}
其基本逻辑时将 open 传入的参数转换为 lfs_file_open 使用的参数,传入 lfs_file_oen, 然后分配一个空闲的文件描述符作为返回值。
在 _read 和 _write 接口中对文件描述符进行判断,当文件描述符为 0/1/2 时将数据重定向到串口,否则从文件中读写数据。
代码如下:
int _write(int fd, char *pBuffer, int size)
{
int res = 0;
if (fd == 1 || fd ==2)
{
HAL_UART_Transmit(&huart3, (uint8_t *)pBuffer, size, size);
}
else
{
res = lfs_file_write(&g_lfs, g_file_list[fd], pBuffer, size);
}
return res;
}
完成以上步骤后,便可以在程序中使用 open/read/write 等接口访问文件系统了,测试程序如下:
fs_init();
write(STDOUT_FILENO, "system init ...\n", 17);
mkdir("/data", 0755);
fd = open("/data/ascii.txt", O_CREAT|O_WRONLY);
for (ch = 32; ch < 126; ch++)
{
write(fd, &ch, 1);
}
close(fd);
fd = open("/data/ascii.txt", O_RDONLY);
while (1)
{
char buff[16];
int res = read(fd, buff, 16);
if (res < 0)
{
close(fd);
break;
}
printf("system tick: %"PRIu32"\n", HAL_GetTick());
printf("read file data:%.*s\n", 16, buff);
HAL_Delay(500);
}
程序下载烧录后,使用串口工具查看到一下数据:
移植的用途
关于在 STM32 中使用系统 IO 的尝试,主要是为了在 STM32 上移植一些 Linux 下的第三方库。
他们很多都不可避免的使用了文件 IO 和 Posix 线程接口,对于 Posix 线程的接口在 FreeRTOS 中有提供,但是系统 IO 却没有找到什么合适的方案,于是有了这样的一种尝试。
现在好像已经有了更好的方案而不用去移植,不过使用这种方式的好处是以较少的代码可以将系统 IO 和标准 IO 进行关联。
关于半主机模式
最后提一下半主机模式:这个实质上是提供了一个在调试时访问主机数据的方法:
通过触发 SVC 指令,在 R0 寄存器中传入需要的系统调用 ID, 在 R1 寄存器中传入参数结构体的指针。
通过调试器,可以在主机接受到对应的系统调用,并进行相应的处理。