本程序所用的单片机型号为:STM32F103VE,晶振:8MHz
文件存储器:容量为16MB的W25Q128
开发工具:Keil MDK 5
程序实现了用C语言<stdio.h>里面的fopen,fread,fwrite,fclose等标准库函数打开W25Q128中的文件。当然还是用到了FatFS库,通过重写Keil的_sys_open、_sys_close、_sys_write、_sys_read等函数,实现fopen与FatFS的f_open的绑定。
_ttywrch函数是abort函数内部调用的串口字符输出函数。
程序使用的是标准C库,没有使用MicroLIB。项目属性中没有勾选Use MicroLIB复选框。
#pragma import(__use_no_semihosting)这句话的作用是禁用半主机模式。默认情况下,fopen函数是通过调用ARM调试器的指令读取调试器所连接的电脑上的文件,如果程序运行在非调试模式下则会直接产生Hard Error错误,这就是所谓的半主机模式。只要程序重写_sys_*系列的函数,就能关闭半主机模式,实现我们想要的功能。加上了这句话之后,编译器就能帮我们检测并提醒看哪些函数需要重写却没有重写,避免Hard Error错误产生。
程序的下载地址:https://pan.baidu.com/s/16-jnl_03HhA_TYaxbNj3aw
主程序:
#include <errno.h>
#include <ff.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static FATFS fatfs;
static int file_init(void)
{
char buffer[FF_MIN_SS];
FRESULT fr;
fr = f_mount(&fatfs, "0:/", 1); // 挂载磁盘
if (fr == FR_OK)
{
printf("f_mount OK!\n");
return 1;
}
if (fr == FR_NO_FILESYSTEM)
{
// 若磁盘没有格式化, 则格式化磁盘
fr = f_mkfs("0:/", FM_FAT, 0, buffer, sizeof(buffer));
if (fr == FR_OK)
{
printf("Disk is formatted!\n");
return 1;
}
else
{
printf("Disk cannot be formatted! fr=%d\n", fr);
return 0;
}
}
else
{
printf("f_mount error! fr=%d\n", fr);
return 0;
}
}
static void errno_test(void)
{
double a = 0.0;
a = sin(3.4 / a);
printf("a=%lf, sin=0x%p, errno=%d\n", a, sin, errno);
perror("error");
errno = 0;
perror("error");
}
static void file_test(void)
{
FILE *fp;
int num;
fp = fopen("hello7.txt", "r+");
if (fp == NULL) // 若文件不存在, 则创建一个
{
fp = fopen("hello7.txt", "w+");
if (fp == NULL)
{
printf("Cannot open File 1!\n");
return;
}
printf("File 1 is created!\n");
fputs("=0", fp);
rewind(fp);
}
else
printf("File 1 is open!\n");
fscanf(fp, "=%d", &num);
printf("num=%d\n", num);
rewind(fp);
fprintf(fp, "=%d", num + 1);
printf("ftell=%ld\n", ftell(fp));
fclose(fp);
}
static void file_test2(void)
{
char buffer[50];
char *s = "This is a string! Hello World!";
FILE *fp;
int n;
fp = fopen("foo.txt", "r");
if (fp == NULL)
{
fp = fopen("foo.txt", "w");
if (fp == NULL)
{
printf("Cannot open File 2!\n");
return;
}
n = fwrite(s, 1, strlen(s), fp);
printf("%d bytes written!\n", n);
fclose(fp);
return;
}
n = fread(buffer, 1, sizeof(buffer) - 1, fp);
if (n > 0)
{
buffer[n] = '\0';
printf("File 2: %s\n", buffer);
}
else
printf("fread failed! ret=%d\n", n);
fclose(fp);
}
int main(int argc, char **argv)
{
int i, ret;
// 串口USART1是在_sys_open的usart_init函数中初始化的
// 命令行参数由_sys_command_string函数的返回值指定
printf("Hello World! argc=%d\n", argc);
for (i = 0; argv[i] != NULL; i++)
printf("ARG %d: %s\n", i, argv[i]);
#if 1
printf("Please input a number: ");
ret = scanf("%d", &i); // 请用Tera Term软件测试这个函数 (不要用普通的串口调试工具)
if (ret == 1)
printf("3*i=%d\n", 3 * i);
else
{
printf("Not a number!");
fflush(stdout); // 强制显示未换行的输出
abort(); // 异常中止程序
}
#endif
errno_test();
// 使用文件函数前必须先挂载磁盘
ret = file_init();
if (!ret)
return -1;
file_test();
file_test2();
return 0; // 可通过重写_sys_exit函数接收主函数的返回值
}
// 由于用到了FatFS库, 所以必须把启动文件中的栈大小改大
void HardFault_Handler(void)
{
printf("Hard Error!\n");
printf("Please check Stack_Size and Heap_Size in startup_stm32f10x_hd.s!\n");
while (1);
}
把标准库函数与FatFS API绑定在一起的代码:
/* Redefining target-dependent system I/O functions in the C library */
/* http://www.keil.com/support/man/docs/armlib/armlib_chr1358938932518.htm */
#include <ctype.h>
#include <rt_sys.h>
#include <stdint.h>
#include "usart.h"
// 是否将fopen与FatFS关联起来
#define FATFS_EN 1
#if FATFS_EN
#include <ff.h>
#include <stdlib.h>
#endif
#pragma import(__use_no_semihosting) // 禁用半主机模式
#define STDIN 0
#define STDOUT 1
#define STDERR 2
#define IS_STD(fh) ((fh) >= 0 && (fh) <= 2)
/*
* These names are used during library initialization as the
* file names opened for stdin, stdout, and stderr.
* As we define _sys_open() to always return the same file handle,
* these can be left as their default values.
*/
const char __stdin_name[] = ":tt";
const char __stdout_name[] = ":tt";
const char __stderr_name[] = ":tt";
FILEHANDLE _sys_open(const char *name, int openmode)
{
#if FATFS_EN
BYTE mode;
FIL *fp;
FRESULT fr;
#endif
if (name == __stdin_name)
return STDIN;
else if (name == __stdout_name)
{
usart_init(); // 初始化串口 (在main函数执行前执行)
return STDOUT;
}
else if (name == __stderr_name)
return STDERR;
#if FATFS_EN
if (sizeof(FILEHANDLE) < sizeof(void *))
{
usart_send_string("sizeof(FILEHANDLE) should be no less than sizeof(void *)!\n", -1);
return -1;
}
fp = malloc(sizeof(FIL));
if (fp == NULL)
return -1;
/* http://elm-chan.org/fsw/ff/doc/open.html */
if (openmode & OPEN_W)
{
mode = FA_CREATE_ALWAYS | FA_WRITE;
if (openmode & OPEN_PLUS)
mode |= FA_READ;
}
else if (openmode & OPEN_A)
{
mode = FA_OPEN_APPEND | FA_WRITE;
if (openmode & OPEN_PLUS)
mode |= FA_READ;
}
else
{
mode = FA_READ;
if (openmode & OPEN_PLUS)
mode |= FA_WRITE;
}
fr = f_open(fp, name, mode);
if (fr == FR_OK)
return (uintptr_t)fp;
free(fp);
#endif
return -1;
}
int _sys_close(FILEHANDLE fh)
{
#if FATFS_EN
FRESULT fr;
#endif
if (IS_STD(fh))
{
if (fh == STDOUT)
usart_deinit();
return 0;
}
#if FATFS_EN
fr = f_close((FIL *)fh);
if (fr == FR_OK)
{
free((void *)fh);
return 0;
}
#endif
return -1;
}
int _sys_write(FILEHANDLE fh, const unsigned char *buf, unsigned len, int mode)
{
#if FATFS_EN
FRESULT fr;
UINT bw;
#endif
if (fh == STDIN)
return -1;
if (fh == STDOUT || fh == STDERR)
{
usart_send_string((const char *)buf, len);
return 0;
}
#if FATFS_EN
fr = f_write((FIL *)fh, buf, len, &bw);
if (fr == FR_OK)
return len - bw;
#endif
return -1;
}
int _sys_read(FILEHANDLE fh, unsigned char *buf, unsigned len, int mode)
{
char ch;
int i = 0;
#if FATFS_EN
FRESULT fr;
UINT br;
#endif
if (fh == STDIN)
{
while (i < len)
{
ch = usart_receive();
if (isprint(ch))
{
buf[i++] = ch;
usart_send(ch);
}
else if (ch == '\r')
{
buf[i++] = '\n';
usart_send('\n');
break;
}
else if (i > 0 && ch == '\b')
{
i--;
usart_send_string("\b \b", 3);
}
}
return len - i;
}
else if (fh == STDOUT || fh == STDERR)
return -1;
#if FATFS_EN
fr = f_read((FIL *)fh, buf, len, &br);
if (fr == FR_OK)
return len - br;
#endif
return -1;
}
void _ttywrch(int ch)
{
usart_send(ch);
}
int _sys_istty(FILEHANDLE fh)
{
return IS_STD(fh);
}
int _sys_seek(FILEHANDLE fh, long pos)
{
#if FATFS_EN
FRESULT fr;
if (!IS_STD(fh))
{
fr = f_lseek((FIL *)fh, pos);
if (fr == FR_OK)
return 0;
}
#endif
return -1;
}
long _sys_flen(FILEHANDLE fh)
{
#if FATFS_EN
if (!IS_STD(fh))
return f_size((FIL *)fh);
#endif
return -1;
}
// 用来接收main函数返回值的函数, 这个函数不允许返回
void _sys_exit(int returncode)
{
while (1);
}
// 定义main函数argv的内容
char *_sys_command_string(char *cmd, int len)
{
// 可以把命令行内容放入大小为len的cmd缓存区然后返回
// 也可以直接返回一个字符串
return "./foo -f bar";
}
如果只想实现printf函数重定向到USART1串口输出,则只需要实现fputc函数并重定义__stdout变量就行了(不勾选Use MicroLIB):
/* Redefining low-level library functions to enable direct use of high-level library functions in the C library */
/* http://www.keil.com/support/man/docs/armlib/armlib_chr1358938931411.htm */
#include <stdio.h>
#include <stm32f10x.h>
#pragma import(__use_no_semihosting) // 禁用半主机模式
struct __FILE
{
int handle;
/* Whatever you require here. If the only file you are using is */
/* standard output using printf() for debugging, no file handling */
/* is required. */
};
/* FILE is typedef'd in stdio.h. */
FILE __stdout;
int fputc(int ch, FILE *fp)
{
if (fp == stdout)
{
if (ch == '\n')
{
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 等待前一字符发送完毕
USART_SendData(USART1, '\r');
}
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, ch);
}
return ch;
}
// 用来接收main函数返回值的函数, 这个函数不允许返回
void _sys_exit(int returncode)
{
while (1);
}
int main(void)
{
GPIO_InitTypeDef gpio;
USART_InitTypeDef usart;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
// 串口发送引脚PA9设为复用推挽输出, 串口接收引脚PA10保持默认的浮空输入
gpio.GPIO_Mode = GPIO_Mode_AF_PP;
gpio.GPIO_Pin = GPIO_Pin_9;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio);
USART_StructInit(&usart);
usart.USART_BaudRate = 115200;
USART_Init(USART1, &usart);
USART_Cmd(USART1, ENABLE);
printf("STM32F103VE USART1\n");
return 0;
}
void HardFault_Handler(void)
{
printf("Hard Error!\n");
while (1);
}
请注意,以上两个程序,在项目属性中不能将Use MicroLIB勾选上!否则程序不能运行!!!