基于stm32 的fatfs0.07e移植过程分享
前段时间移植了fats文件系统,并基于这个文件系统,做了个简单的wav文件播放器,可以播放sd卡根目录下music 文件夹里所有pcm编码的wav文件,并且支持下一曲、上一曲、暂停、开始操作。在这个过程中遇到了很多问题,在这里把我的移植经历和大家分享。
硬件平台:英蓓特 EM-STM3210E开发板
软件平台:mdk3.40;stm32V2.01库
FATfs版本:R0.07e。可在https://www.360docs.net/doc/185559162.html,/fsw/ff/00index_e.html下载
EM-STM3210E开发板上的sd卡例程只是简单的写和读sd卡的某个扇区,当然这样写过后卡上的文件系统格式就被破坏了,正因为这样,运行过板上的示例程序后我就不爽——要把卡重新格一遍。
在网上找了找,看到fatfs文件系统的移植也挺简单的,于是就搜集了fat文件系统的一些资料开始看,准备看完后做移植。
移植工作需要做的就是修改diskio.c和diskio.h这两个文件,给sd卡的底层和文件系统做个接口。
具体的工作就是完成如下几个函数:
disk_initialize ();//卡初始化
disk_status (); //返回卡的状态
disk_read (); //读扇区
disk_write (); //写扇区
disk_ioctl (); //支持几个命令
get_fattime (); //给fatfs提供时间
其中:
disk_initialize (); disk_read (); disk_read ();三个函数比较重要。
在进行上面函数的编写前,我们一定要保证我们已经写好了sd卡的底层,如卡的初始化,读一个扇区、读多个扇区、写一个扇区、写多个扇区等。我的开发板上这些功能函数比较全,sd卡的访问使用sd模式,可选择查询、中断、DMA三种方式进行操作,当然DMA方式是最快的。
/**************************************************************************
功能:磁盘初始化
***************************************************************************/
DSTATUS disk_initialize (BYTE drv )// Physical drive nmuber (0..)
{
SD_Error Status;
if(drv)
{
return STA_NOINIT; //仅支持磁盘0的操作
}
Status = SD_Init();
if(Status != SD_OK)
{
return STA_NOINIT; //其他错误:初始化失败
}
else
{
Status = SD_GetCardInfo(&SDCardInfo); //读sd卡信息
if (Status != SD_OK)
{
return STA_NOINIT;//RES_NOTRDY; //报NOT READY错误
}
// Select Card
Status = SD_SelectDeselect((u32) (SDCardInfo.RCA << 16)); if (Status != SD_OK)
{
return STA_NOINIT;//RES_NOTRDY; //报NOT READY错误
}
switch(mda_or_interrupt)
{
case 1: //dma方式
Status = SD_EnableWideBusOperation(SDIO_BusWide_4b);
if (Status != SD_OK)
{
return RES_NOTRDY; //报NOT READY错误
}
Status = SD_SetDeviceMode(SD_DMA_MODE);
if (Status != SD_OK)
{
return RES_NOTRDY; //报NOT READY错误
}
break;
case 0: //中断方式
Status = SD_EnableWideBusOperation(SDIO_BusWide_1b);
if (Status != SD_OK)
{
return RES_NOTRDY; //报NOT READY错误
}
Status = SD_SetDeviceMode(SD_INTERRUPT_MODE);
if (Status != SD_OK)
{
return RES_NOTRDY; //报NOT READY错误
}
break;
default :
break;
}
return 0; //初始化成功
}
}
下面的函数一般可以直接返回0,也可用 sd_ncd 脚检测有无sd卡
/**************************************************************************
功能:Return Disk Status 可用 sd_ncd 脚检测有无sd卡有: 拉低 ; 无: 浮空
***************************************************************************/ DSTATUS disk_status (BYTE drv) // Physical drive nmuber (0..)
{
if(drv)
{
return STA_NOINIT; //仅支持磁盘0的操作
}
return 0; //初始化成功
}
/**************************************************************************
功能: Read Sector(s)
***************************************************************************/ DRESULT disk_read (
BYTE drv, /* Physical drive nmuber (0..) */
BYTE *buff, /* Data buffer to store read data */
DWORD sector, /* Sector address (LBA) */
BYTE count /* Number of sectors to read (1..255) */
)
{
vu8 res1 = 0, res2 = 0;
SD_Error Status;
if (drv || !count)
{
return RES_PARERR; //仅支持单磁盘操作,count不能等于0,否则返回参数错误 }
switch(mda_or_interrupt)
{
case 1: //dma方式
if(count==1) //1个sector的读操作
{
Status = SD_ReadBlock(sector << 9,buff2,BlockSize);//sector<<9 扇区地址转为字节地址一个扇区512字节 memcpy(buff,buff2,BlockSize);
res1=buff[510]; res2=buff[511]; //调试用
}
else //多个sector的读操作
{
Status = SD_ReadMultiBlocks(sector << 9,buff2,BlockSize,count);
memcpy(buff,buff2,BlockSize * count);
}
break;
case 0: //中断方式
if(count==1) //1个sector的读操作
{
Status = SD_ReadBlock(sector<<9,(u32 *)(&buff[0]),BlockSize);
res1=buff[510]; res2=buff[511]; //调试用
}
else //多个sector的读操作
{
Status = SD_ReadMultiBlocks(sector<<9 ,(u32 *)(&buff[0]),BlockSize,coun
t);
}
break;
default :
break;
}
//处理返回值,将sdcard.c的返回值转成ff.c的返回值
if(Status == SD_OK)
return RES_OK;
else
return RES_ERROR;
}
/**************************************************************************
功能: Write Sector(s)
***************************************************************************
/
#if _READONLY == 0
DRESULT disk_write (
BYTE drv, /* Physical drive nmuber (0..) */
const BYTE *buff, /* Data to be written */
DWORD sector, /* Sector address (LBA) */
BYTE count /* Number of sectors to write (1..255) */
)
{
SD_Error Status;
if (drv || !count)
{
return RES_PARERR; //仅支持单磁盘操作,count不能等于0,否则返回参数错误
}
switch(mda_or_interrupt)
{
case 1: //dma方式
if(count==1) //1个sector的写操作
{
memcpy(buff2,buff,BlockSize);
Status = SD_WriteBlock(sector << 9,buff2,BlockSize);//sector<<9 扇区地址转为字节地址一个扇区512字节
}
else //多个sector的写操作
{
memcpy(buff2,buff,BlockSize * count);
Status =SD_WriteMultiBlocks(sector << 9,buff2,BlockSize,coun
t);
}
break;
case 0: //中断方式
if(count==1) //1个sector的写操作
{
Status = SD_WriteBlock(sector << 9 ,(u32 *)(&buff[0]),BlockSiz
e);
}
else //多个sector的写操作
{
Status = SD_WriteMultiBlocks(sector << 9 ,(u32 *)(&buff[0]),BlockSize,coun
t);
}
default :
break;
}
//处理返回值,将sdcard.c的返回值转成ff.c的返回值
if(Status == SD_OK)
return RES_OK;
else
return RES_ERROR;
}
#endif /* _READONLY */
/**************************************************************************
功能: Miscellaneous s
***************************************************************************/ DRESULT disk_ioctl (
BYTE drv, /* Physical drive nmuber (0..) */
BYTE ctrl, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
u32 x, y, z;
DRESULT res;
if (drv)
{
return RES_PARERR; //仅支持单磁盘操作,否则返回参数错误
}
//FATFS目前版本仅需处理CTRL_SYNC,GET_SECTOR_COUNT,GET_BLOCK_SIZ三个命令 switch(ctrl)
{
case CTRL_SYNC:
if(SD_GetTransferState()==SD_NO_TRANSFER)
{
res = RES_OK;
}
else
{
res = RES_ERROR;
}
case GET_BLOCK_SIZE:
*(WORD*)buff = 512;
res = RES_OK;
break;
case GET_SECTOR_COUNT: //读卡容量
////formula of the capacity///////////////
//
// memory capacity = BLOCKNR * BLOCK_LEN
//
// BLOCKNR = (C_SIZE + 1)* MULT
//
// C_SIZE_MULT+2
// MULT = 2
//
// READ_BL_LEN
// BLOCK_LEN = 2
//////////////////////////////////////////
if (SD_GetCardInfo(&SDCardInfo)==SD_OK)//读sd卡信息 {
x=SDCardInfo.SD_csd.DeviceSize+1; //C_SIZE + 1 y=SDCardInfo.SD_csd.DeviceSizeMul+2; //C_SIZE_MULT+2 z=SDCardInfo.SD_csd.RdBlockLen+y;
*(DWORD*)buff =x< res = RES_OK; } else { res = RES_ERROR ; } break; default: res = RES_PARERR; break; } return res; } /*-----------------------------------------------------------------------*/ /* User defined to give a current time to fatfs module */ /* 31-25: Year(0-127 org.1980), 24-21: Month(1-12), 20-16: Day(1-31) * / /* 15-11: Hour(0-23), 10-5: Minute(0-59), 4-0: Second(0-29 *2) */ /*-----------------------------------------------------------------------* / DWORD get_fattime (void) { struct tm t; DWORD date; t = Time_GetCalendarTime(); t.tm_year -= 1980; //年份改为1980年起 t.tm_mon++; //0-11月改为1-12月 t.tm_sec /= 2; //将秒数改为0-29 date = 0; date = (t.tm_year << 25) | (t.tm_mon<<21) | (t.tm_mday<<16)|\ (t.tm_hour<<11) | (t.tm_min<<5) | (t.tm_sec); return date; } //此函数参考网友九九的程序 当这些函数写好后,移植工作就完成了。 我们在使用时在main.c中加入#i nclude “ccsbcs.c” ,并在 ffconf.h里做如下修改 #define _USE_LFN 1 #define_LFN_UNICODE 0//不使用UNICODE 移植完后我进行了读写速度测试:10M文件连续写入、读出。 1s中断计时 DMA 方式:读 49k-50K byte/s 写 1M byte/s 中断方式:读 47k byte/s 写 0.83M byte/s wav文件的播放,我主要是先遍历music文件夹下所有wav格式的文件,然后把文件名写入play_list.txt,播放时,从play_list.txt读出歌曲名,根据歌曲名读出数据送DA。 /*************************************************************** 功能:从music 文件夹里读出所有wav格式的歌曲名写入 play_list.txt 返回 name_num ****************************************************************/ //////////////////////////////////////////////////////////////// //当一个文件名的长度小于 8+1+3时长文件名区是空的;当长度大于12时 //长文件名区才会是这个文件的真实文件名,而这是短文件名区是经过计算后 //的文件名,并不是真实的文件名 //////////////////////////////////////////////////////////////// FRESULT scan_files (char* path) { FRESULT res; FILINFO fno; DIR dir; int i,j; char *fn; char namebuf_1[fn_buf_len]={0}; static u8 fs_type_copy; #if _USE_LFN //为长文件名区指针赋初值 static char lfn[_MAX_LFN * (_DF1S ? 2 : 1) + 1]; fno.lfname = lfn; fno.lfsize = sizeof(lfn); #endif name_num=0; res =f_mount(0, &fs); //每次 open一个对象时都先要注册一个缓冲区 res = f_unlink ("music/play_list.txt");//每次重建文件名时,先删掉原来的。 res = f_opendir(&dir, path); // 打开指定的目录 if (res == FR_OK) { for (;;) { repet: //第一次执行读一个文件,第二次执行读第二个文件……………… res = f_readdir(&dir, &fno);//读出目录里的文件信息存入 fno fs_type_copy=dir.fs->fs_type; if (res != FR_OK || fno.fname[0] == 0) break; #if _USE_LFN { fn = *fno.lfname ? fno.lfname : fno.fname; j = *fno.lfname ? (fno.lfsize-1): (8+1+3 -1); } #else { fn = fno.fname; j=8+1+3 -1; } #endif for(i=j;i>=3;i--) //文件名从右到左判断当文件扩展名为 wav时成立 { if ((*(fn+i-2) == 'w')&&(*(fn+i-1) == 'a')&&(*(fn+i) == 'v')) { *(fn+i+1)='\0';//为文件名添加字符串结束符 /////////////// 成功检测到相符的文件了 //对文件名进行存储存入"music/play_list.txt" ///////////////////////////////////////////// //f_open 函数的型参指的是从根目录开始的文件名称 //故muxic文件夹下的goon.wav 文件的名字要写做 "music/goon.wav" ///////////////////////////////////////////// for(j=0;j {namebuf[j]=0;} namebuf[0]='m';namebuf[1]='u';namebuf[2]='s';namebuf[3]='i';namebuf[4]='c';namebuf[5]='/'; memcpy(&namebuf[6],fn,(i+2)); for(j=0;j //文件名是不是写完一遍了 if(!strcmp(namebuf,namebuf_1)) { res = f_close (&F); res = f_mount(0, NULL); return FR_OK; //相等返回0 } if(updata_list==1){updata_list=0;memcpy(namebuf_1,namebuf,fn_buf_len);} // res =f_mount(0, &fs); res = f_open (&F,"music/play_list.txt", FA_CREATE_ALWAYS |FA_OPEN_EXISTING | FA_WRITE|FA_READ); res = f_lseek (&F, name_num*fn_buf_len ); res = f_write (&F, namebuf, fn_buf_len, &rw_num); res = f_sync (&F); name_num++; dir.fs->fs_type=fs_type_copy; //dir 不知怎么被改动了,导致无法正确读下一个文件 // 改动的原因是: f_mount(0,&fs)时 fs->fs_type=0;而下次f_readdir时就用了 fs这个缓冲 // 只 f_mount(0,&fs1)而不f_mount(0,&fs)? goto repet; } } goto repet; } } return res; } 输入模式初始化GPIOE2,3,4 ①IO口初始化:GPIO_InitTypeDef GPIO_InitStructure; ②使能PORTA,PORTE时钟: RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE); ③PE.2.3.4端口配置:GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4; ④设置成(上拉)输入:GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; ⑤GPIO_Init(GPIOE, &GPIO_InitStructure); 输出模式初始化 ①IO口初始化:GPIO_InitTypeDef GPIO_InitStructure; ②使能PB,PE端口时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE); ③3LED0-->PB.5 端口配置GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; ④设置(推挽)输出模式GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; ⑤设置IO口速度为50MHz GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; ⑥说明初始化哪个端口GPIO_Init(GPIOB, &GPIO_InitStructure); 在LED灯试验中初始为高电平灭GPIO_SetBits(GPIOB,GPIO_Pin_5); 再初始化相同发输出模式时③④⑤可省略例如(经实验初始化恰好为不同IO口相同IO序号③可省略,应该不规范吧) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED1-->PE.5 端口配置, 推挽输出GPIO_Init(GPIOE, &GPIO_InitStructure); //推挽输出,IO口速度为50MHz GPIO_SetBits(GPIOE,GPIO_Pin_5); //PE.5 输出高 1,头文件可以定义所用的函数列表,方便查阅你可以调用的函数; 2,头文件可以定义很多宏定义,就是一些全局静态变量的定义,在这样的情况下,只要修改头文件的内容,程序就可以做相应的修改,不用亲自跑到繁琐的代码内去搜索。 3,头文件只是声明,不占内存空间,要知道其执行过程,要看你头文件所申明的函数是在哪个.c文件里定义的,才知道。 4,他并不是C自带的,可以不用。 5,调用了头文件,就等于赋予了调用某些函数的权限,如果你要算一个数的N次方,就要调用Pow()函数,而这个函数是定义在math.c里面的,要用这个函数,就必需调用math.h 这个头文件。 STM32启动文件详解 (2012-07-28 11:22:34) 转载▼ 分类:STM32 标签: stm32 启动 在< STM32F103 系列芯片的系统架构: 系统结构: 在每一次复位以后,所有除SRAM 和FLITF 以外的外设都被关闭,在使用一个外设之前,必须设置寄存器RCC_AHBENR 来打开该外设的时钟。 GPIO 输入输出,外部中断,定时器,串口。理解了这四个外设,基本就入门了一款MCU。 时钟控制RCC: -4~16M 的外部高速晶振 -内部8MHz 的高速RC 振荡器 -内部40KHz低速RC 振荡器,看门狗时钟 -内部锁相环(PLL,倍频),一般系统时钟都是外部或者内部高速时钟经过PLL 倍频后得到 - 外部低速32.768K 的晶振,主要做RTC 时钟源 ARM存储器映像: 数据字节以小端格式存放在存储器中。一个字里的最低地址字节被认为是该字的最低有效字节,而最高地址字节是最高有效字节。 存储器映像与寄存器映射: ARM 存储器映像 4GB 0X0000 00000X1FFF FFFF 0X2000 00000X3FFF FFFF 0X4000 00000X5FFF FFFF 寄存器说明: 寄存器名称 相对外设基地址的偏移值 编号 位表 读写权限 寄存器位 功能说明 使用C语言封装寄存器: 1、总线和外设基地址封装利用地址偏移 (1)定义外设基地址(Block2 首地址) (2)定义APB2总线基地址(相对外设基地址偏移固定) (3)定义GPIOX外设基地址(相对APB2总线基地址偏移固定)(4)定义GPIOX寄存器地址(相对GPIOX外设基地址偏移固定)(5)使用 C 语言指针操作寄存器进行读/写 //定义外设基地址 #define PERIPH_BASE ((unsigned int)0x40000000) 1) //定义APB2 总线基地址 #define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000) 2) //定义GPIOC 外设基地址 #define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800) 3) //定义寄存器基地址这里以GPIOC 为例 #define GPIOC_CRL *(unsigned int*)(GPIOC_BASE+0x00) 4) #define GPIOC_CRH *(unsigned int*)(GPIOC_BASE+0x04) #define GPIOC_IDR *(unsigned int*)(GPIOC_BASE+0x08) #define GPIOC_ODR *(unsigned int*)(GPIOC_BASE+0x0C) #define GPIOC_BSRR *(unsigned int*)(GPIOC_BASE+0x10) #define GPIOC_BRR *(unsigned int*)(GPIOC_BASE+0x14) #define GPIOC_LCKR *(unsigned int*)(GPIOC_BASE+0x18) //控制GPIOC 第0 管脚输出一个低电平5) GPIOC_BSRR = (0x01<<(16+0)); //控制GPIOC 第0 管脚输出一个高电平 GPIOC_BSRR = (0x01<<0); Keil4 建立STM32工程详解 1:安装mdk412,用注册机注册,这个过程不详细叙述了。 2:在本地某个路径下建立STM32工程文件夹,命名:my_STM32,并在my_STM32下建立rvmdk文件夹,并在rvmdk文件夹内建立 obj,list两个文件夹。 3: 打开Keil4. 4: 选择Project菜单->New uVision Project...,选择.../my_STM32/rvmdk文件夹的路径,并命名工程文件:my_STM32,回车 5:选择器件名称,见图1 图1 单击OK。 6:如图2所示:选择否,不添加Startup.s,以后自己添加。 图2 7:如图3,建立几个Group:startup(即将装入启动文件等),usr(即将装入应用程序文件),FWlib(即将装入库文件的.c文件),doc(即将装入说明文档) 图3 8:右键单击FWlib,Add Files to Group 'FWlib',选择库文件的路径下的src 文件内的所有文件,并点击Add,如图4所示: 图4 9:将cortexm3_macro.s,stm32f10x_vector.s,stm32f10x_it.c, stm32f10x_it.h,stm32f10x_conf.h,main.c,readme.txt拷贝到my_STM32文件夹内。 10:右键单击usr,Add Files to Group 'usr',选择main.c,stm32f10x_it.c,stm32f10x_it.h,stm32f10x_conf.h,并Add,如图5所示 这是前段时间做彩屏显示时候遇到的难题, *(__IO uint16_t *) (Bank1_LCD_C)这个就是将后面的数据转换为地址,然后对地址单元存放数据。可如下等效: __IO uint16_t *addr; addr = (__IO uint16_t *) Bank1_LCD_C; #ifdef和#elif连用,语法和if。。。else if语句一样 推挽输出增加驱动,可以驱动LED起来 static int count=0 count++ 这个语句中,count仅仅被初始化一次 以后加加一次期中的值就不会变化了 SysTick_CTRL(控制和状态寄存器) SysTick_LOAD(重装载寄存器) SysTick_VAL(当前值寄存器) SysTick_CALIB(校准值寄存器) TFT经验:弄多大的相片,必须先把那个相片的尺寸改掉,再去取模,才可以,要不会有重影的嘿嘿嘿嘿 VBAT 是电池供电的引脚 VBAT和ADD同时都掉电时才能让备份区复位。 volatile一个变量的存储单元可以在定义该变量的程序之外的某处被引用。 volatile主要是程序员要告诉编译器不要对其定义的这个变量进行优化,防止其不能被引用,不能被改变。 VDDA>2.4V ADC才能工作 VDDA>2.7V USB才能工作 VDD(1.8-3.6v) VBAT=1.8-3.6v VSS VSSA VREF必须接到地线 没有外部电源供电时必须VBAT接上VDD 使用PLL时,VDDA必须供电 printf("abs(x)=%d\n",x<0?(-1)*x:x) 条件编译是问号前边为真则取冒号前边的值,为假的,则取后边的值。 所以说上边这条打印的语句是打印x的绝对值。 //stm32f10x_nvic.c stm32f10x_lib.c stm32f10x_gpio.c stm32f10x_flash.c stm32f10x_rcc.c TIM6 TIM7基本定时器 (只有这两个定时器不能产生PWM) TIM1 TIM8高级控制定时器 TIM2 TIM3 TIM4 TIM5为通用定时器 其中高级定时器TIM1和TIM8可以同时产生多达7路的PWM输出。而通用定时器也能同时产生多达4路的PWM输出,这样,STM32最多可以同时产生30路PWM输出! 修改和自己写代码时候 STM32固件库详解 最近考试较多,教材编写暂停了一下,之前写了很多,只是每一章都感觉不是特别完整,最近把其中的部分内容贴出来一下,欢迎指正。本文内容基于我对固件库的理解,按照便于理解的顺序进行整理介绍,部分参考了固件库的说明,但是也基本上重新表述并按照我理解的顺序进行重新编写。我的目的很简单,很多人写教程只是告诉你怎么做,不会告诉你为什么这么做,我就尽量吧前因后果都说清楚,这是我的出发点,水平所限,难免有很大的局限性,具体不足欢迎指正。基于标准外设库的软件开发 STM32标准外设库概述 STM32标准外设库之前的版本也称固件函数库或简称固件库,是一个固件函数包,它由程序、数据结构和宏组成,包括了微控制器所有外设的性能特征。该函数库还包括每一个外设的驱动描述和应用实例,为开发者访问底层硬件提供了一个中间API,通过使用固件函数库,无需深入掌握底层硬件细节,开发者就可以轻松应用每一个外设。因此,使用固态函数库可以大大减少用户的程序编写时间,进而降低开发成本。每个外设驱动都由一组函数组成,这组函数覆盖了该外设所有功能。每个器件的开发都由一个通用API (application programming interface 应用编程界面)驱动,API对该驱动程序的结构,函数和参数名称都进行了标准化。 ST公司2007年10月发布了版本的固件库,MDK 之前的版本均支持该库。2008年6月发布了版的固件库,从2008年9月推出的MDK 版本至今均使用版本的固件库。以后的版本相对之前的版本改动较大,本书使用目前较新的版本。 使用标准外设库开发的优势 简单的说,使用标准外设库进行开发最大的优势就在于可以使开发者不用深入了解底层硬件细节就可以灵活规范的使用每一个外设。标准外设库覆盖了从GPIO到定时器,再到CAN、I2C、SPI、UART和ADC 等等的所有标准外设。对应的C源代码只是用了最基本的C编程的知识,所有代码经过严格测试,易于理解和使用,并且配有完整的文档,非常方便进行二次开发和应用。 STM32F10XXX标准外设库结构与文件描述 1. 标准外设库的文件结构 在上一小节中已经介绍了使用标准外设库的开发的优势,因此对标准外设库的熟悉程度直接影响到程序的编写,下面让我们来认识一下STM32F10XXX的标准外设库。STM32F10XXX的标准外设库经历众多的更新目前已经更新到最新的版本,开发环境中自带的标准外设库为版本,本书中以比较稳定而且较新的版本为基础介绍标准外设库的结构。 STM32学习笔记——时钟频率 ******************************** 本学习笔记基于STM32固件库V3.0 使用芯片型号:STM32F103 开发环境:MDK ******************************** 第一课时钟频率 STM32F103内部8M的内部震荡,经过倍频后最高可以达到72M。目前TI的M3系列芯片最高频率可以达到80M。 在stm32固件库3.0中对时钟频率的选择进行了大大的简化,原先的一大堆操作都在后台进行。系统给出的函数为SystemInit()。但在调用前还需要进行一些宏定义的设置,具体的设置在system_stm32f10x.c文件中。 文件开头就有一个这样的定义: //#define SYSCLK_FREQ_HSE HSE_Value //#define SYSCLK_FREQ_20MHz 20000000 //#define SYSCLK_FREQ_36MHz 36000000 //#define SYSCLK_FREQ_48MHz 48000000 //#define SYSCLK_FREQ_56MHz 56000000 #define SYSCLK_FREQ_72MHz 72000000 ST 官方推荐的外接晶振是8M,所以库函数的设置都是假定你的硬件已经接了8M 晶振来运算的.以上东西就是默认晶振8M 的时候,推荐的CPU 频率选择.在这里选择了: #define SYSCLK_FREQ_72MHz 72000000 也就是103系列能跑到的最大值72M 然后这个C文件继续往下看 #elif defined SYSCLK_FREQ_72MHz const uint32_t SystemFrequency = SYSCLK_FREQ_72MHz; const uint32_t SystemFrequency_SysClk = SYSCLK_FREQ_72MHz; const uint32_t SystemFrequency_AHBClk = SYSCLK_FREQ_72MHz; const uint32_t SystemFrequency_APB1Clk = (SYSCLK_FREQ_72MHz/2); const uint32_t SystemFrequency_APB2Clk = SYSCLK_FREQ_72MHz; 这就是在定义了CPU跑72M的时候,各个系统的速度了.他们分别是:硬件频率,系统时 钟,AHB总线频率,APB1总线频率,APB2总线频率.再往下看,看到这个了: #elif defined SYSCLK_FREQ_72MHz static void SetSysClockTo72(void); 这就是定义72M 的时候,设置时钟的函数.这个函数被SetSysClock ()函数调用,而SetSysClock ()函数则是被SystemInit()函数调用.最后SystemInit()函数,就是被你调用的了 startup_stm32f10x_xx.s 启动代码文件选择startup_stm32f10x_cl.s 互联型的器件,STM32F105xx,STM32F107xx startup_stm32f10x_hd.s 大容量的STM32F101xx,STM32F102xx,STM32F103xx startup_stm32f10x_hd_vl.s 大容量的STM32F100xx startup_stm32f10x_ld.s 小容量的STM32F101xx,STM32F102xx,STM32F103xx startup_stm32f10x_ld_vl.s 小容量的STM32F100xx startup_stm32f10x_md.s 中容量的STM32F101xx,STM32F102xx,STM32F103xx startup_stm32f10x_md_vl.s 中容量的STM32F100xx startup_stm32f10x_xl.s FLASH在512K到1024K字节的STM32F101xx,STM32F102xx,STM32F103xx 固件库中的Release_Notes_for_STM32F10x_CMSIS.html写到: STM32F10x CMSIS Startup files: startup_stm32f10x_xx.s Add new startup files for STM32 Low-density Value line devices: startup_stm32f10x_ld_vl.s Add new startup files for STM32 Medium-density Value line devices: startup_stm32f10x_md_vl.s SystemInit() function is called from startup file (startup_stm32f10x_xx.s) before to branch to applic ation main. To reconfigure the default setting of SystemInit() function, refer to system_stm32f10x.c file GNU startup file for Low density devices (startup_stm32f10x_ld.s) is updated to fix compilation err ors. 例如我用STM32F103RB,那么选启动文件为startup_stm32f10x_md.s STM32启动代码概述 一般嵌入式开发流程就是先建立一个工程,再编写源文件,然后进行编译,把所有的*.s文件和*.c文件编译成一个*.o文件,再对目标文件进行链接和定位,编译成功后会生成一个*.hex文件和调试文件,接下来要进行调试,如果成功的话,就可以将它固化到flash里面去。 启动代码是用来初始化电路以及用来为高级语言写的软件作好运行前准备的一小段汇编语言,是任何处理器上电复位时的程序运行入口点。 比如,刚上电的过程中,PC机会对系统的一个运行频率进行锁定在一个固定的值,这个设计频率的过程就是在汇编源代码中进行的,也就是在启动代码中进行的。与此同时,设置完后,程序开始运行,注意,程序是在内存中运行的。这个时候,就需要把一些源文件从flash里面copy到内存中,又要对它们进行初始化读写,这又有频率的设置。这些都是初始化。 初始化完成后,我们又要设置一些堆栈,要跳到C语言的main函数里面运行。这就需要堆栈。对普通的ARM CPU有这样一个要求:在绝对地址为零的地方要放置一个异常向量表,但并不是所有的ARM CPU都留有这个一个空间,这就需要用到映射的功能。我们可以将其它地方的一些空间映射到绝对地址里面。当发生异常时,ARM核来读取异常中断表的时候,它会使用映射之后的那个表,这个就可以接着往下执行,否则在绝对地址零的地方找不到任何信息,程序就会死掉。这些运行的环境全部建立好后,程序就会跳转到我们的main函数里面。 总之,启动代码,就是对最小系统的初始化。包括晶振,CPU频率等。 启动代码的最小系统是:异常向量表的初始化–存储区分配–初始化堆栈–高级语言入口函数调用– main()函数。 程序的启动过程: STM32学习笔记(5):通用定时器PWM输出 2011年3月30日TIMER输出PWM 1.TIMER输出PWM基本概念 脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽度的控制。一般用来控制步进电机的速度等等。 STM32的定时器除了TIM6和TIM7之外,其他的定时器都可以用来产生PWM输出,其中高级定时器TIM1和TIM8可以同时产生7路的PWM输出,而通用定时器也能同时产生4路的PWM输出。 1.1PWM输出模式 STM32的PWM输出有两种模式,模式1和模式2,由TIMx_CCMRx寄存器中的OCxM位确定的(“110”为模式1,“111”为模式2)。模式1和模式2的区别如下: 110:PWM模式1-在向上计数时,一旦TIMx_CNT 1、GPIO函数: 输出: HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET);//此例以PA12口为例 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_SET); //此例以PA12口为例 HAL_GPIO_ TogglePin(GPIOA,GPIO_PIN_12); //此例以PA12口为例 2、串口函数: 1、串口发送/接收函数 HAL_UART_Transmit();串口轮询模式发送,使用超时管理机制 HAL_UART_Receive();串口轮询模式接收,使用超时管理机制 HAL_UART_Transmit_IT();串口中断模式发送 HAL_UART_Receive_IT();串口中断模式接收 HAL_UART_Transmit_DMA();串口DMA模式发送 HAL_UART_Transmit_DMA();串口DMA模式接收 2、串口中断函数 HAL_UART_TxHalfCpltCallback();一半数据发送完成时调用 HAL_UART_TxCpltCallback();数据完全发送完成后调用 HAL_UART_RxHalfCpltCallback();一半数据接收完成时调用 HAL_UART_RxCpltCallback();数据完全接受完成后调用 HAL_UART_ErrorCallback();传输出现错误时调用 例程:串口接收中断 uint8_t aTxStartMessages[] = "\r\n******UART commucition using IT******\r\nPlease enter 10 characters:\r\n"; uint8_t aRxBuffer[20]; 2、在main函数中添加两个语句通过串口中断发送aTxStartMessage数组的数据和接收数据10个字符,保存在数组aRxBuffer中 HAL_UART_Transmit_IT(&huart1 ,(uint8_t*)aTxStartMessages,sizeof(aTxStartMessages)); //sizeof()可读取目标长度 HAL_UART_Receive_IT(&huart1,(uint8_t*)aRxBuffer,10); 3、在main.c文件后面添加中断接收完成函数,将接收到的数据又通过串口发送回去。 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { UNUSED(huart); HAL_UART_Transmit(&huart1,(uint8_t*)aRxBuffer,10,0xFFFF);//(uint8_t*)aRxBuffer为字符串地址,10为字符串长度,0xFFFF为超时时可以在中间加任何可执行代码。 } STM32启动文件详解 一、启动文件的作用 1.初始化堆栈指针SP; 2.初始化程序计数器指针PC; 3.设置堆、栈的大小; 4.设置异常向量表的入口地址; 5.配置外部SRAM作为数据存储器(这个由用户配置,一般的开发板可没有外部SRAM); 6.设置C库的分支入口__main(最终用来调用main函数); 7.在版的启动文件还调用了在文件中的SystemIni()函数配置系统时钟。 二、汇编指令 三、启动代码 ----- 栈 Stack_Size EQU 0x00000400 ; 栈的大小 AREA STACK, NOINIT, READWRITE,ALIGN=3 Stack_Mem SPACE Stack_Size ; 分配栈空间 __initial_sp ; 栈的结束地址(栈顶地址) 分配名为STACK,不初始化,可读可写,8(2^3)字节对齐的1KB空间。 栈:局部变量,函数形参等。栈的大小不能超过内部SRAM大小。 AREA:汇编一个新的代码段或者数据段。STACK段名,任意命名;NOINIT表示不初始化;READWRITE可读可写;ALIGN=3(2^3= 8字节对齐)。 __initial_sp紧挨了SPACE放置,表示栈的结束地址,栈是从高往低生长,结束地址就是栈顶地址。 ----- 堆 Heap_Size EQU 0x00000200 ; 堆的大小(512Bytes) AREA HEAP, NOINIT, READWRITE,ALIGN=3 __heap_base ; 堆的起始地址 Heap_Mem SPACE Heap_Size ; 分配堆空间 __heap_limit ; 堆的结束地址 分配名为HEAP,不初始化,可读可写,8(2^3)字节对齐的512字节空间。__heap_base堆的起始地址,__heap_limit堆的结束地址。堆由低向高生长。动态分配内存用到堆。 PRESERVE8 -- 指定当前文件的堆/栈按照8 字节对齐。 THUMB-- 表示后面指令兼容THUMB 指令。THUBM 是ARM 以前的指令集,16bit;现在Cortex-M 系列的都使用THUMB-2 指令集,THUMB-2 是32 位的,兼容16 位和32 位的指令,是THUMB 的超级。 3.向量表 AREA RESET, DATA, READONLY EXPORT __Vectors E XPORT __Vectors_End E XPORT __Vectors_Size 定义一个名为RESET,可读的数据段。并声明__Vectors、__Vectors_End 和__Vectors_Size 这三个标号可被外部的文件使用。 __Vectors DCD __initial_sp ; Top of Stack DCD Reset_Handler ; Reset Handler STM32学习心得笔记 时钟篇 在STM32中,有五个时钟源,为HSI、HSE、LSI、LSE、PLL。 ①、HSI是高速内部时钟,RC振荡器,频率为8MHz。 ②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为 4MHz~16MHz。 ③、LSI是低速内部时钟,RC振荡器,频率为40kHz。 ④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。 ⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍, 但是其输出频率最大不得超过72MHz。 其中40kHz的LSI供独立看门狗IWDG使用,另外它还可以被选择为实时时钟RTC的时钟源。另外, 实时时钟RTC的时钟源还可以选择LSE,或者是HSE的128分频。RTC的时钟源通过RTCSEL[1:0]来选择。 STM32中有一个全速功能的USB 模块,其串行接口引擎需要一个频率为48MHz的时 钟源。该时钟源只能 从PLL输出端获取,可以选择为1.5分频或者1分频,也就是,当需要使用USB模块时,PLL 必须使能, 并且时钟频率配置为48MHz或72MHz。 另外,STM32还可以选择一个时钟信号输出到MCO脚(PA8)上,可以选择为PLL输出的2分频、HSI、HSE、或者系统时钟。 系统时钟SYSCLK,它是供STM32中绝大部分部件工作的时钟源。系统时钟可选择为PLL 输出、HSI或者HSE。系统时钟最 大频率为72MHz,它通过AHB分频器分频后送给各模块使用,AHB分频器可选择1、2、4、8、16、64、128、256、512分 频。其中AHB分频器输出的时钟送给5大模块使用: ①、送给AHB 总线、内核、内存和DMA使用的HCLK时钟。 ②、通过8分频后送给Cortex的系统定时器时钟。 ③、直接送给Cortex的空闲运行时钟FCLK。 ④、送给APB1分频器。APB1分频器可选择1、2、4、8、16分频,其输出一路供APB1外设使用(PCLK1,最大频率36MHz), 另一路送给定时器(Timer)2、3、4倍频器使用。该倍频器可选择1或者2倍频,时钟输出供定时器2、3、4使用。 在< 数据的保存和毁灭(2) 和以前学到的有关数据保存不同,这里的数据保存还有“保密”之意,即一旦受到意外的侵入,STM32将毁灭数据。这是通过Tamper机制来实现的。 以下是数据手册中的有关说明: 5.3.1 侵入检测 当TAMPER引脚上的信号从0变成1或者从1变成0(取决于备份控制寄存器BKP_CR的TPAL 位),会产生一个侵入检测事件。侵入检测事件将所有数据备份寄存器内容清除。然而为了避免丢失侵入事件,侵入检测信号是边沿检测的信号与侵入检测允许位的逻辑与,从而在侵入检测引脚被允许前发生的侵入事件也可以被检测到。 ●当 TPAL=0 时:如果在启动侵入检测TAMPER引脚前(通过设置TPE位)该引脚已经为高电平,一旦启动侵入检测功能,则会产生一个额外的侵入事件(尽管在TPE位置’1’后并没有出现上升沿)。 ●当 TPAL=1 时:如果在启动侵入检测引脚TAMPER前(通过设置TPE位)该引脚已经为低电平,一旦启动侵入检测功能,则会产生一个额外的侵入事件(尽管在TPE位置’1’后并没有出现下降沿)。 设置BKP_CSR寄存器的TPIE位为’1’,当检测到侵入事件时就会产生一个中断。 在一个侵入事件被检测到并被清除后,侵入检测引脚TAMPER应该被禁止。然后,在再次写入备份数据寄存器前重新用TPE位启动侵入检测功能。这样,可以阻止软件在侵入检测引脚上仍然有侵入事件时对备份数据寄存器进行写操作。这相当于对侵入引脚TAMPER进行电平检测。 注:当V DD电源断开时,侵入检测功能仍然有效。为了避免不必要的复位数据备份寄存器,TAMPER引脚应该在片外连接到正确的电平。 显然,Tamper需要硬件与之配合。以上数据手册描述了硬件配置时的一些注意事项。 (1)可以是把引脚由低电平到高电平认为是一次侵入,也可以把引脚从高电平变到低电平认为是一次侵入,这通过TPAL来设置。 阅读flash:芯片内部存储器flash操作函数我的理解——对芯片内部flash进行操作的函数,包括读取,状态,擦除,写入等等,可以允许程序去操作flash上的数据。 基础应用1,FLASH时序延迟几个周期,等待总线同步操作。推荐按照单片机系统运行频率,0—24MHz时,取Latency=0;24—48MHz时,取Latency=1;48~72MHz时,取Latency=2。 所有程序中必须的 用法:FLASH_SetLatency(FLASH_Latency_2); 位置:RCC初始化子函数里面,时钟起振之后。 基础应用2,开启FLASH预读缓冲功能,加速FLASH的读取。 所有程序中必须的 用法:FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); 位置:RCC初始化子函数里面,时钟起振之后。 3、阅读lib:调试所有外设初始化的函数。 我的理解——不理解,也不需要理解。只要知道所有外设在调试的时候,EWRAM需要从这个函数里面获得调试所需信息的地址或者指针之类的信息。 基础应用1,只有一个函数debug。所有程序中必须的。 用法:#ifdef DEBUG debug(); #endif 位置:main函数开头,声明变量之后。 4、阅读nvic:系统中断管理。 我的理解——管理系统内部的中断,负责打开和关闭中断。 基础应用1,中断的初始化函数,包括设置中断向量表位置,和开启所需的中断两部分。 所有程序中必须的。 用法:void NVIC_Configuration(void) { NVIC_InitTypeDef NVIC_InitStructure; //中断管理恢复默认参数 #ifdef VECT_TAB_RAM //如果C/C++ Compiler\Preprocessor\Defined symbols中的定义了 VECT_TAB_RAM(见程序库更改内容的表格) NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0); //则在RAM调试 #else //如果没有定义VECT_TAB_RAM NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);//则在Flash里调试 #endif //结束判断语句 //以下为中断的开启过程,不是所有程序必须的。 //NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC优先级分组,方式。 //注:一共16个优先级,分为抢占式和响应式。两种优先级所占的数量由此代码确定, NVIC_PriorityGroup_x可以是0、1、2、3、4,分别代表抢占优先级有1、2、4、8、16个和响应优先级有16、8、4、2、1个。规定两种优先级的数量后,所有的中断级别必须在其中选择,抢占级别高的会打断其他中断优先执行,而响应级别高的会在其他中断执行完优先执行。 //NVIC_InitStructure.NVIC_IRQChannel = 中断通道名; //开中断,中断名称见函数库 //NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级 //NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //响应优先级 一、原子位操作: 原子位操作定义在文件中。令人感到奇怪的是位操作函数是对普通的内存地址进行操作的。原子位操作在多数情况下是对一个字长的内存访问,因而位号该位于0-31之间(在64位机器上是0-63之间),但是对位号的范围没有限制。 原子操作中的位操作部分函数如下: void set_bit(int nr, void *addr)原子设置addr所指的第nr位 void clear_bit(int nr, void *addr)原子的清空所指对象的第nr位 void change_bit(nr, void *addr)原子的翻转addr所指的第nr位int test_bit(nr, void *addr)原子的返回addr位所指对象nr位int test_and_set_bit(nr, void *addr)原子设置addr所指对象的第nr位,并返回原先的值 int test_and_clear_bit(nr, void *addr)原子清空addr所指对象的第nr位,并返回原先的值 int test_and_change_bit(nr, void *addr)原子翻转addr所指对象的第nr位,并返回原先的值 unsigned long word = 0; set_bit(0, &word); /*第0位被设置*/ set_bit(1, &word); /*第1位被设置*/ clear_bit(1, &word); /*第1位被清空*/ change_bit(0, &word); /*翻转第0位*/ 二、STM32的GPIO锁定: 三、中断挂起: 因为某种原因,中断不能马上执行,所以“挂起”等待。比如有高、低级别的中断同时发生,就挂起低级别中断,等高级别中断程序执行完,在执行低级别中断。四、固文件: 固件(Firmware)就是写入EROM(可擦写只读存储器)或EEPROM(电可擦可编程只读存储器)中的程序。 五、固件库:包含各个外设或者内核的驱动头文件和C文件。 六、TIx的输入捕获滤波器(消抖): 采样频率fSAMPLING,采样次数N,如果以采样频率对一脉冲进行采样时,如果在N个采样方波里该脉宽不变,则视为一次有效的脉冲,否则视为无效的脉冲。 七、高级定时器的PWM互补输出: 常用于X相电机驱动,其中的互补输出则防止电机的死区出现。STM32学习笔记
STM32启动文件详解
STM32学习笔记_STM32F103ZET6
Keil4 建立STM32工程详解
stm32学习 c语言笔记
STM32固件库详解42324
STM32学习笔记
STM32F10x 启动代码文件选择
STM32启动概述
STM32学习笔记(5)通用定时器PWM输出
STM32学习笔记
stm32启动文件详解
STM32学习心得笔记
STM32之启动文件详细解析(V3.5.0)讲解
STM32学习笔记(18)-数据的保存和毁灭
STM32入门C语言详解精编版
STM32自学笔记