【方法】Keil 5下STM32F103VE单片机通过C语言本身的fopen函数打开存储在W25Q128存储器中的文件

本程序所用的单片机型号为: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勾选上!否则程序不能运行!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值