- #ifndef SD_DRIVER_H_
- #define SD_DRIVER_H_
- #include "main.h"
- // SD SPI初始化定义
- #define SD_SPIx SPI2
- #define SD_SPI_PERIPH RCC_APB1_PERIPH_SPI2
- #define SD_SPI_PIN_PORT GPIOB
- #define SD_SPI_PIN_PERIPH RCC_APB2_PERIPH_GPIOB
- #define SD_SPI_PIN_MISO GPIO_PIN_14
- #define SD_SPI_PIN_MOSI GPIO_PIN_15
- #define SD_SPI_PIN_SCK GPIO_PIN_13
- #define SD_SPI_PIN_CS GPIO_PIN_12
- //SD卡类型
- #define ERR 0x00
- #define MMC 0x01
- #define V1 0x02
- #define V2 0x04
- #define V2HC 0x06
- #define DUMMY_BYTE 0xFF
- #define MSD_BLOCKSIZE 512
- //CMD定义
- #define CMD0 0 //卡复位
- #define CMD1 1
- #define CMD8 8 //命令8 ,SEND_IF_COND
- #define CMD9 9 //命令9 ,读CSD数据
- #define CMD10 10 //命令10,读CID数据
- #define CMD12 12 //命令12,停止数据传输
- #define CMD16 16 //命令16,设置扇区大小(SectorSize) 应返回0x00
- #define CMD17 17 //命令17,读扇区(sector)
- #define CMD18 18 //命令18,读多个扇区(Multi sector)
- #define CMD23 23 //命令23,设置多扇区(sector)写入前预先擦除N个block
- #define CMD24 24 //命令24,写扇区(sector)
- #define CMD25 25 //命令25,写多个扇区(Multi sector)
- #define CMD41 41 //命令41,应返回0x00
- #define CMD55 55 //命令55,应返回0x01
- #define CMD58 58 //命令58,读OCR信息
- #define CMD59 59 //命令59,使能/禁止CRC,应返回0x00
- //数据写入回应字意义
- #define MSD_DATA_OK 0x05
- #define MSD_DATA_CRC_ERROR 0x0B
- #define MSD_DATA_WRITE_ERROR 0x0D
- #define MSD_DATA_OTHER_ERROR 0xFF
- //SD卡回应标记字
- #define MSD_RESPONSE_NO_ERROR 0x00
- #define MSD_IN_IDLE_STATE 0x01
- #define MSD_ERASE_RESET 0x02
- #define MSD_ILLEGAL_COMMAND 0x04
- #define MSD_COM_CRC_ERROR 0x08
- #define MSD_ERASE_SEQUENCE_ERROR 0x10
- #define MSD_ADDRESS_ERROR 0x20
- #define MSD_PARAMETER_ERROR 0x40
- #define MSD_RESPONSE_FAILURE 0xFF
- #define SD_CS_LOW GPIO_ResetBits(SD_SPI_PIN_PORT,SD_SPI_PIN_CS);
- #define SD_CS_HIGH GPIO_SetBits(SD_SPI_PIN_PORT,SD_SPI_PIN_CS);
- // SD卡SPI初始化
- void SD_SPI_Init(void);
- // SD初始化
- DSTATUS SD_Init(void);
- // 读盘
- DRESULT SD_ReadDisk(uint8_t*buf,uint32_t sector,uint8_t cnt);
- // 写盘
- DRESULT SD_WriteDisk(uint8_t* buf,uint32_t sector,uint8_t cnt);
- #endif
代码编写:
- // SD_Driver.c
- #include "SD_Driver.h"
- // SD卡类型
- uint8_t SD_TYPE=0x00;
- // SPI初始化状态,防止反复进行初始化
- uint8_t SD_SPI_Init_Status = 0;
- SPI_InitType SD_SPI_Struct;
- // SD卡SPI初始化
- void SD_SPI_Init(void)
- {
- if(SD_SPI_Init_Status)return ;
-
- SD_SPI_Init_Status = 1;
-
- // 开启GPIO和复用时钟
- RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_AFIO | SD_SPI_PIN_PERIPH, ENABLE);
- // 根据配置的SPI对象开启SPI时钟
- if(SD_SPI_PERIPH == RCC_APB2_PERIPH_SPI1){
- RCC_EnableAPB2PeriphClk(SD_SPI_PERIPH, ENABLE);
- }else{
- RCC_EnableAPB1PeriphClk(SD_SPI_PERIPH, ENABLE);
- }
-
- // GPIO初始化
- GPIO_InitType SD_GPIO_Struct;
- GPIO_InitStruct(&SD_GPIO_Struct);
-
- SD_GPIO_Struct.GPIO_Mode = GPIO_Mode_AF_PP;
- SD_GPIO_Struct.GPIO_Speed = GPIO_Speed_50MHz;
- SD_GPIO_Struct.Pin = SD_SPI_PIN_MOSI | SD_SPI_PIN_SCK;
- GPIO_InitPeripheral(SD_SPI_PIN_PORT,&SD_GPIO_Struct);
-
- SD_GPIO_Struct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
- SD_GPIO_Struct.Pin = SD_SPI_PIN_MISO ;
- GPIO_InitPeripheral(SD_SPI_PIN_PORT,&SD_GPIO_Struct);
-
- SD_GPIO_Struct.GPIO_Mode = GPIO_Mode_Out_PP;
- SD_GPIO_Struct.Pin = SD_SPI_PIN_CS ;
- GPIO_InitPeripheral(SD_SPI_PIN_PORT,&SD_GPIO_Struct);
- SD_CS_HIGH;
-
- // 初始化SPI1
- SPI_InitStruct(&SD_SPI_Struct);
-
- SD_SPI_Struct.DataDirection = SPI_DIR_DOUBLELINE_FULLDUPLEX; // 工作模式:全双工
- SD_SPI_Struct.SpiMode = SPI_MODE_MASTER; // SPI主从配置:主机模式
- SD_SPI_Struct.DataLen = SPI_DATA_SIZE_8BITS; // 数据长度:8bit
- SD_SPI_Struct.CLKPOL = SPI_CLKPOL_HIGH; // 时钟极性:高
- SD_SPI_Struct.CLKPHA = SPI_CLKPHA_SECOND_EDGE; // 触发沿:第二个边沿触发
- SD_SPI_Struct.NSS = SPI_NSS_SOFT; // NSS控制:软件控制
- SD_SPI_Struct.BaudRatePres = SPI_BR_PRESCALER_256; // 分频系数
- SD_SPI_Struct.FirstBit = SPI_FB_MSB; // 数据传输高位优先
- SD_SPI_Struct.CRCPoly = 7; // CRC检验
- SPI_Init(SD_SPIx, &SD_SPI_Struct);
- // 开启SPI
- SPI_Enable(SD_SPIx,ENABLE);
-
- }
- // SPI数据读写
- uint8_t SPIx_ReadWriteByte(uint8_t byte)
- {
- /* 等待数据发送寄存器清空 */
- while (SPI_I2S_GetStatus(SD_SPIx, SPI_I2S_TE_FLAG) == RESET);
-
- /* 通过SPI发送出去一个字节数据 */
- SPI_I2S_TransmitData(SD_SPIx, byte);
-
- /* 等待接收到一个数据(接收到一个数据就相当于发送一个数据完毕) */
- while (SPI_I2S_GetStatus(SD_SPIx, SPI_I2S_RNE_FLAG) == RESET);
-
- /* 返回接收到的数据 */
- return SPI_I2S_ReceiveData(SD_SPIx);
- }
- // SPI速度设置
- void SPIx_SetSpeed(u8 SpeedSet)
- {
- SD_SPI_Struct.BaudRatePres = SpeedSet ;
- SPI_Init(SD_SPIx, &SD_SPI_Struct);
- SPI_Enable(SD_SPIx,ENABLE);
- }
SD 卡初始化
- ///////////////////////////////////////////////////////////////
- //发送命令,发完释放
- //////////////////////////////////////////////////////////////
- int SD_sendcmd(uint8_t cmd,uint32_t arg,uint8_t crc)
- {
- DRESULT r1;
- uint8_t retry;
- SD_CS_LOW;
- systick_delay_us(20);
- //SD_CS_ON;
- do{
- retry=SPIx_ReadWriteByte(0xFF);
- }while(retry!=0xFF);
- SPIx_ReadWriteByte(cmd | 0x40);
- SPIx_ReadWriteByte(arg >> 24);
- SPIx_ReadWriteByte(arg >> 16);
- SPIx_ReadWriteByte(arg >> 8);
- SPIx_ReadWriteByte(arg);
- SPIx_ReadWriteByte(crc);
- if(cmd==CMD12)SPIx_ReadWriteByte(0xFF);
- do
- {
- r1=SPIx_ReadWriteByte(0xFF);
- }while(r1&0X80);
-
- return r1;
- }
- //读取指定长度数据
- DRESULT SD_ReceiveData(uint8_t *data, uint16_t len)
- {
- uint8_t r1;
- SD_CS_LOW;
-
- do
- {
- r1 = SPIx_ReadWriteByte(0xFF);
- systick_delay_us(100);
- }while(r1 != 0xFE);
-
- while(len--)
- {
- *data = SPIx_ReadWriteByte(0xFF);
- data++;
- }
-
- SPIx_ReadWriteByte(0xFF);
- SPIx_ReadWriteByte(0xFF);
- return RES_OK;
- }
- //向sd卡写入一个数据包的内容 512字节
- DRESULT SD_SendBlock(uint8_t* buf,uint8_t cmd)
- {
- uint16_t t;
- uint8_t r1;
- do{
- r1=SPIx_ReadWriteByte(0xFF);
- }while(r1!=0xFF);
-
- SPIx_ReadWriteByte(cmd);
- if(cmd!=0XFD)//不是结束指令
- {
- for(t=0;t<512;t++)SPIx_ReadWriteByte(buf[t]);//提高速度,减少函数传参时间
- SPIx_ReadWriteByte(0xFF);//忽略crc
- SPIx_ReadWriteByte(0xFF);
- t=SPIx_ReadWriteByte(0xFF);//接收响应
- if((t&0x1F)!=0x05)return RES_WRPRT;//响应错误
- }
- return RES_OK;//写入成功
- }
- //获取CID信息
- uint8_t SD_GETCID (uint8_t *cid_data)
- {
- uint8_t r1;
- r1=SD_sendcmd(CMD10,0,0x01); //读取CID寄存器
- if(r1==0x00){
- r1=SD_ReceiveData(cid_data,16);
- }
- SD_CS_HIGH;
- if(r1)return 1;
- else return 0;
- }
- //获取CSD信息
- uint8_t SD_GETCSD(uint8_t *csd_data){
- uint8_t r1;
- r1=SD_sendcmd(CMD9,0,0x01);//发CMD9命令,读CSD寄存器
- if(r1==0)
- {
- r1=SD_ReceiveData(csd_data, 16);//接收16个字节的数据
- }
- SD_CS_HIGH;//取消片选
- if(r1)return 1;
- else return 0;
- }
- //获取SD卡的总扇区数
- uint32_t SD_GetSectorCount(void)
- {
- uint8_t csd[16];
- uint32_t Capacity;
- uint8_t n;
- uint16_t csize;
- //取CSD信息,如果期间出错,返回0
- if(SD_GETCSD(csd)!=0) return 0;
- //如果为SDHC卡,按照下面方式计算
- if((csd[0]&0xC0)==0x40) //V2.00的卡
- {
- csize = csd[9] + ((uint16_t)csd[8] << 8) + 1;
- Capacity = (uint32_t)csize << 10;//得到扇区数
- }
- else//V1.XX的卡
- {
- n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
- csize = (csd[8] >> 6) + ((uint16_t)csd[7] << 2) + ((uint16_t)(csd[6] & 3) << 10) + 1;
- Capacity= (uint32_t)csize << (n - 9);//得到扇区数
- }
- return Capacity;
- }
- //
- u32 GetSDCardSectorCount(void)
- {
- u8 csd[16];
- u32 Capacity=0;
- u16 csize;
- //获取SD卡的CSD信息,包括容量和速度信息,存放CID的内存,至少16Byte
- SD_sendcmd(CMD9,0,0x01);//发SDCard_CMD9命令,读CSD
- SD_ReceiveData(csd,16);//接收16个字节的数据
- SD_CS_HIGH;//取消片选
- SPIx_ReadWriteByte(0xff);//提供额外的8个时钟
- if((csd[0]&0xC0)==0x40) //SDHC卡,按照下面方式计算
- {
- csize=csd[9]+(csd[8]<<8)+1;
- Capacity=csize<<10;//得到扇区数
- }
- return Capacity;
- }
- // SD卡初始化
- DSTATUS SD_Init(void)
- {
- /*1. 初始化底层IO口*/
- SD_SPI_Init();
-
- // 设置SPI速度,初始化时速度尽量低,防止初始化失败,初始化完成后尽量提高速度
- SPIx_SetSpeed(SPI_BR_PRESCALER_256);
- SD_CS_LOW;
- /*2. 发送最少74个脉冲*/
- for(uint8_t i=0;i<10;i++)SPIx_ReadWriteByte(0xFF);
-
-
- /*3. 进入闲置状态*/
- uint8_t r1;
- do{
- r1 = SD_sendcmd(CMD0 ,0, 0x95);
- }while(r1!=0x01);
-
-
- uint8_t buff[6] = {0};
- uint16_t retry;
-
- /*4. 鉴别SD卡类型*/
- SD_TYPE = 0;
- if(SD_sendcmd(CMD8,0x1AA,0x87)==0x01)
- {
- for(uint8_t i=0;i<4;i++)buff[i]=SPIx_ReadWriteByte(0xFF); //Get trailing return value of R7 resp
- //printf("R7 Resp:%02X %02X %02X %02X\n",buff[0],buff[1],buff[2],buff[3]);
- if(buff[2] == 0x01 && buff[3] == 0xAA)
- {
- do
- {
- SD_sendcmd(CMD55,0,0x01); //发送SDCard_CMD55
- r1=SD_sendcmd(CMD41,0x40000000,0x01);//发送SDCard_CMD41
- }while(r1);
- if(SD_sendcmd(CMD58,0,0x01)==0) //鉴别SD2.0卡版本开始
- {
- for(uint8_t i=0;i<4;i++)buff[i]=SPIx_ReadWriteByte(0XFF);//得到OCR值
-
- if(buff[0]&0x40)
- {
- SD_TYPE=V2HC;
- }
- else
- {
- SD_TYPE=V2;
- }
- }
- }
- else
- {
- SD_sendcmd(CMD55,0,0X01); //发送CMD55
- r1=SD_sendcmd(CMD41,0,0X01); //发送CMD41
- if(r1<=1)
- {
- SD_TYPE=V1;
- retry=0XFFFE;
- do //等待退出IDLE模式
- {
- SD_sendcmd(CMD55,0,0X01); //发送CMD55
- r1=SD_sendcmd(CMD41,0,0X01);//发送CMD41
- }while(r1&&retry--);
- }else//MMC卡不支持CMD55+CMD41识别
- {
- SD_TYPE=MMC;//MMC V3
- retry=0XFFFE;
- do //等待退出IDLE模式
- {
- r1=SD_sendcmd(CMD1,0,0X01);//发送CMD1
- }while(r1&&retry--);
- }
- if(retry==0||SD_sendcmd(CMD16,512,0X01)!=0)SD_TYPE=ERR;//错误的卡
- }
- }
- char cardType[10]={0};
- switch(SD_TYPE)
- {
- case MMC:{ strcpy(cardType,"MMC");break;}
- case V1:{ strcpy(cardType,"SDV1");break;}
- case V2:{ strcpy(cardType,"SDV2");break;}
- case V2HC:{ strcpy(cardType,"SDHC");break;}
- default:{ strcpy(cardType,"ERROR");break;}
- }
- printf("CartType=%s\n",cardType);
-
- SD_CS_HIGH; //取消片选
- SPIx_ReadWriteByte(0xff);//提供额外的8个时钟
- SPIx_SetSpeed(SPI_BR_PRESCALER_8);
- if(SD_TYPE)return FR_OK;
- else return FR_INT_ERR;
- }
至此,主要的SPI工作就完成了,我们回到 main.h中来测试下能不能成功驱动起来// main.h
#include "main.h"
#include "log.h"
#include "SD_Driver.h"
int main(void)
{
log_init();
SD_Init();
// 获取扇区数和大小
uint32_t Sector = SD_GetSectorCount();
printf("SectorCount=%d Size=%.2fMB\n",Sector,(Sector/(1024*1024.0f))*MSD_BLOCKSIZE);
while(1)
{
}
}
我的是32G的TF卡,实际打印消息如下:
如果出现这样的信息,就说明我们SD卡已经初始化成功了,接下来就可以愉快的进行文件系统的移植了!
2、FatFS文件系统的移植
1)配置说明
因为最终我们是希望实现在SD卡中拷入固件文件方式进行升级,而我们之前配置的并不能获取到文件系统的信息,就是无法直接通过文件名来访问到文件,所以接下来我们就要移植下文件系统,这里使用的是目前嵌入式系统广泛使用的FatFS文件系统。
开始移植前可能是一头雾水,不知道从哪开始,要做些什么!其实,如果能明白FatFS移植要处理些什么,哪移植起来就非常的简单,如果不明白,哪就是非常的艰难,我因为没有找到一篇非常清楚讲解移植要做些什么,所以花了差不多三天时间才算是搞定,因为从各种各样的资料中获取到自己所需的信息,花费了太多查找资料的时间。
请看下图:
从上图中,我们可以看到,移植就是需要处理状态获取、进**初始化、然后实现读与写,移植过程其实就是需要实现图上的几个接口的功能就可以了,剩下的工作就交给FatFS。所以说会者不难,难者不会,就是这个道理。
2)开始配置
现在我们就开始移植吧,首先我们访问 fatfs 的官方网站: http://elm-chan.org/fsw/ff/00index_e.html,将页面滚动到最后,找到箭头所指的位置,点击后可以跳到所有发布的下载地址页面:
我这里显示的最新版本是 FatFs R0.15 (跳到这个页面的原理就是怕之后版本会有变化,而这里会有所有版本,以免版本不一致导致报错等情况的出现)
下载后解压,可以看到 documents(文档目录)和source(源码目录),而source下就是我们需要的文件内容
在我们工程目录下新建一个文件夹 FATFS,将 source 目录下所有的.h .c文件复制过去,然后在Keil5中将新建一个组 FATFS,将所有的 .c 加进去,并且在配置里将头文件路径中将FATFS加入,以下是配置细节:
最后点 Ok,软件部分就配置完了。
3)移植及配置- // ffconf.h 文件
- // 文件系统配置
- // 是否启用只读 0不启用
- #define FF_FS_READONLY 0
- // 是否启用 格式化 改为1 启用
- #define FF_USE_MKFS 1
- // 是否使用启用 seek 改为1 启用
- #define FF_USE_FASTSEEK 1
- // 字符编码 包含中文所以使用936
- #define FF_CODE_PAGE 936
- // 是否开始长文件名 ,根据自己需要,启用后固件会成倍增加
- #define FF_USE_LFN 0
- // 驱动器号 我们这里使用SD卡,所以改为2
- #define FF_VOLUMES 2
- // 无RTC,本次没有配置RTC,所以改为1
- #define FF_FS_NORTC 1
- // 是否启用 exFat 支持,启用后必须开启 FF_USE_LFN
- #define FF_FS_EXFAT 0
根据前面思维导图中说明,我们需要实现一些初始化函数,而有些我们已经处理了,接着就要驱动文件中添加以下方法及实现了:
// SD_Driver.h
- // SD_Driver.h 添加以下声明
- // FatFs文件系统初始化
- DSTATUS SD_disk_initialize(BYTE pdrv);
- // FatFs文件系统状态
- DSTATUS SD_disk_status(BYTE pdrv);
- // FatFs文件系统ioctl
- DRESULT SD_disk_ioctl(BYTE pdrv,BYTE cmd,void *buff);
// SD_Driver.c
- // SD_Driver.c 添加以下内容
- DSTATUS SD_disk_status(BYTE pdrv)
- {
- DSTATUS stat;
- uint8_t ciddata[16]= {0};
- if(SD_GETCID(ciddata)) //调用SD卡的获取设备状态函数接口
- stat = RES_ERROR;
- else
- stat = RES_OK;
- return stat;
- //return STA_NOINIT;/* Drive not initialized */
- }
- /*@pdrv Physical drive nmuber to identify the drive */
- DSTATUS SD_disk_initialize (BYTE pdrv)
- {
- DSTATUS stat;
- uint8_t ciddata[16]= {0};
- SD_Init();//初始化SD卡
- if(SD_GETCID(ciddata)) //重新获取一下SD卡的ID,看是否初始化成功
- stat = RES_ERROR;
- else
- stat = RES_OK;
- return stat;
- }
- DRESULT SD_disk_ioctl (
- BYTE pdrv, /* Physical drive nmuber (0..) */
- BYTE cmd, /* Control code */
- void *buff /* Buffer to send/receive control data */
- )
- {
- DRESULT res;
- // if(pdrv == DEV_SD)
- // {
- switch (cmd)
- {
- case CTRL_SYNC:
- res = RES_OK;
- break;
- case GET_SECTOR_SIZE:
- *(DWORD*)buff = 512; //每个扇区的大小为512字节
- res = RES_OK;
- break;
- case GET_BLOCK_SIZE:
- *(WORD*)buff = 8;
- res = RES_OK;
- break;
- case GET_SECTOR_COUNT:
- *(DWORD*)buff = SD_GetSectorCount();//调用获取SD卡扇区个数的函数接口
- res = RES_OK;
- break;
- default:
- res = RES_PARERR;
- break;
- }
- //}
- if(res)
- return RES_PARERR;
- return res;
- }
// 配置 diskio.c
- // 配置 diskio.c
- #include "ff.h" /* Obtains integer types */
- #include "diskio.h" /* Declarations of disk functions */
- #include "SD_Driver.h"
- /* Definitions of physical drive number for each drive */
- #define DEV_RAM 0 /* Example: Map Ramdisk to physical drive 0 */
- #define DEV_MMC 1 /* Example: Map MMC/SD card to physical drive 1 */
- #define DEV_USB 2 /* Example: Map USB MSD to physical drive 2 */
- /*-----------------------------------------------------------------------*/
- /* Get Drive Status */
- /*-----------------------------------------------------------------------*/
- DSTATUS disk_status (
- BYTE pdrv /* Physical drive nmuber to identify the drive */
- )
- {
- DSTATUS stat;
- int result;
- switch (pdrv) {
- case DEV_RAM :
- //result = RAM_disk_status();
- // translate the reslut code here
- return stat;
- case DEV_MMC :
- result = SD_disk_status(pdrv);
- // translate the reslut code here
- return result;
- case DEV_USB :
- //result = USB_disk_status();
- // translate the reslut code here
- return stat;
- }
- return STA_NOINIT;
- }
- /*-----------------------------------------------------------------------*/
- /* Inidialize a Drive */
- /*-----------------------------------------------------------------------*/
- DSTATUS disk_initialize (
- BYTE pdrv /* Physical drive nmuber to identify the drive */
- )
- {
- DSTATUS stat;
- int result;
- switch (pdrv) {
- case DEV_RAM :
- //result = RAM_disk_initialize();
- // translate the reslut code here
- return stat;
- case DEV_MMC :
- result = SD_disk_initialize(pdrv);
- // translate the reslut code here
- return result;
- case DEV_USB :
- //result = USB_disk_initialize();
- // translate the reslut code here
- return stat;
- }
- return STA_NOINIT;
- }
- /*-----------------------------------------------------------------------*/
- /* Read Sector(s) */
- /*-----------------------------------------------------------------------*/
- DRESULT disk_read (
- BYTE pdrv, /* Physical drive nmuber to identify the drive */
- BYTE *buff, /* Data buffer to store read data */
- LBA_t sector, /* Start sector in LBA */
- UINT count /* Number of sectors to read */
- )
- {
- DRESULT res;
- int result;
- switch (pdrv) {
- case DEV_RAM :
- // translate the arguments here
- //result = RAM_disk_read(buff, sector, count);
- // translate the reslut code here
- return res;
- case DEV_MMC :
- // translate the arguments here
- result = SD_ReadDisk(buff, sector, count);
- // translate the reslut code here
- return result;
- case DEV_USB :
- // translate the arguments here
- //result = USB_disk_read(buff, sector, count);
- // translate the reslut code here
- return res;
- }
- return RES_PARERR;
- }
- /*-----------------------------------------------------------------------*/
- /* Write Sector(s) */
- /*-----------------------------------------------------------------------*/
- #if FF_FS_READONLY == 0
- DRESULT disk_write (
- BYTE pdrv, /* Physical drive nmuber to identify the drive */
- const BYTE *buff, /* Data to be written */
- LBA_t sector, /* Start sector in LBA */
- UINT count /* Number of sectors to write */
- )
- {
- DRESULT res;
- int result;
- switch (pdrv) {
- case DEV_RAM :
- // translate the arguments here
- //result = RAM_disk_write(buff, sector, count);
- // translate the reslut code here
- return res;
- case DEV_MMC :
- // translate the arguments here
- result = SD_WriteDisk((uint8_t *)buff, sector, count);
- // translate the reslut code here
- return result;
- case DEV_USB :
- // translate the arguments here
- //result = USB_disk_write(buff, sector, count);
- // translate the reslut code here
- return res;
- }
- return RES_PARERR;
- }
- #endif
- /*-----------------------------------------------------------------------*/
- /* Miscellaneous Functions */
- /*-----------------------------------------------------------------------*/
- DRESULT disk_ioctl (
- BYTE pdrv, /* Physical drive nmuber (0..) */
- BYTE cmd, /* Control code */
- void *buff /* Buffer to send/receive control data */
- )
- {
- DRESULT res;
- int result;
- switch (pdrv) {
- case DEV_RAM :
- // Process of the command for the RAM drive
- return res;
- case DEV_MMC :
- // Process of the command for the MMC/SD card
- return res;
- case DEV_USB :
- // Process of the command the USB drive
- return res;
- }
- return RES_PARERR;
- }
我们来测试下,回到 main.h
- #include "main.h"
- #include "log.h"
- #include "SD_Driver.h"
- int main(void)
- {
- log_init();
-
- FATFS *fs;
- fs = malloc(sizeof(FATFS));
- FRESULT fr = f_mount(
- fs, // FATFS对象
- "1:", // 挂载的盘符 相当于win系统下的 C:
- 1 // 挂载选项 0 延迟挂载 1立即挂载
- );
- if(fr == FR_OK)
- {
- printf("挂载SD成功.\n");
- }else if(fr == FR_NO_FILESYSTEM)
- {
- // 没有文件系统
- printf("没有文件系统,开始格式化 ...\n");
-
- BYTE work[FF_MAX_SS]; /* Work area (larger is better for processing time) */
- fr = f_mkfs("1:", 0, work, sizeof work);
- printf("格式化结果: %s %d\r\n", fr==FR_OK?"成功":"失败",fr);
- }
-
- // 开始读写文件测试
- char path[] = "1:test.txt"; // 同样1:是盘符
- char data[] = "Write Data Content.";
-
- // 写入文件测试
- FIL fil;
- fr = f_open(&fil,path,FA_CREATE_ALWAYS | FA_WRITE);
- if(fr == FR_OK)
- {
- UINT br = 0;
- f_write(&fil,data,sizeof(data),&br);
- f_close(&fil);
-
- printf("创建文件成功,写入字节数: %d\n",br);
- }else{
- printf("创建文件失败\n");
- }
-
- // 读取文件测试,读取刚写入的文件内容
- fr = f_open(&fil,path,FA_READ);
- if(fr == FR_OK)
- {
- UINT br = 0;
- char tmp[100]={0};
-
- f_read(&fil,tmp,100,&br);
- f_close(&fil);
-
- printf("读取文件成功,文件内容: %s\n",tmp);
- }else{
- printf("打开文件失败\n");
- }
-
- // 删除文件
- fr = f_unlink(path);
- printf("删除文件结果: %s\n",fr == FR_OK?"成功":"失败");
-
- while(1)
- {
- }
- }
实际执行结果:
三、IAP升级实现
之前我们也分析了下,想要实现IAP,就需要两套程序,一套专门负责升级的叫Bootloader,一套负责业务逻辑的叫APP。而即然是两套程序,都写入到MCU的Flash中,地址肯定要不一样,不然不就后面一个把前面的内容给覆盖了,你说是不是。所以这里就需要对空间进行分配,并且可以方便的进行程序间隔离,相互不影响。
这里用到的开发板是 N32G45XVL-STB v1.1,芯片型号是 N32G457VEL7,Flash 512K (16进制表示 0x80000 ),内存144K (16进制表示 0x24000 )。给Bootloader 划分 16K的存储空间和16K的运行内存,剩余的空间和内存分配给APP。因分配给Bootloader空间有限,所以就没有办法实现非常复杂的功能,这里编写时需要注意。
下面就要编写相应的程序了,为了便于观察,我们使用了板载灯和串口打印信息做为提示,当板载PB5亮起时,表示正在升级程序,当板载PA8闪烁时,表示成功升级并跳到了APP中。
1)Bootloader编写
IROM1(Flash 存储空间) 起始地址是 0x8000000,Bootloader是需要先启动,所以他的起始地址就是 0x8000000 , 16K 就是 0x4000,Size 填入 0x4000。RAM(内存) 起始地址是 0x20000000,16K 是 0x4000,Size 填入 0x4000,Bootloader在Keil5中配置如下:
Bootloader keil5配置
我们先来编写IAP程序,处理Flash写入、跳转等工作:
iap.h- #ifndef IAP_H_
- #define IAP_H_
- #include "main.h"
- #define FLASH_APP_BASE_ADDR 0x08004000 // Bootloader 预留16K空间,APP程序从0x08004000开始
- #define FLASH_START_ADDR FLASH_APP_BASE_ADDR
- #define app_update_flag_addr 0x08040000-4 // APP更新标志,存在BOOT,注意不能覆盖BOOT代码
- typedef void (*iapfun)(void); // 定义一个函数类型的参数.
- void iap_load_app(u32 appxaddr); // 跳转到APP程序执行
- void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 applen); // 在指定地址开始,写入bin
- void IAP_UPDATE_APP(void);
- int32_t app_flag_write(uint32_t data ,uint32_t start_add);
- #endif
iap.c
- #include "iap.h"
- #include "string.h"
- iapfun jump2app;
- uint8_t uart_receiveBIN_ok;
- uint8_t pages_number = 0;
- uint32_t ready_write_addr = 0;
- uint8_t flash_buf[];
- uint8_t receive_app_done;
- __asm void MSR_MSP(u32 addr)
- {
- MSR MSP, r0 //set Main Stack value
- BX r14
- }
- /**================================================================
- APP 跳转
- appxaddr:用户代码起始地址.
- ================================================================*/
- void iap_load_app(u32 appxaddr)
- {
- if(((*(vu32*)appxaddr)&0x0FFFFFFF) < 1024*512) // 检查栈顶地址是否合法.
- {
- jump2app = (iapfun)*(vu32*)(appxaddr+4);
- MSR_MSP(*(vu32*)appxaddr); // 初始化堆栈指针
- jump2app(); // 跳转到APP.
- }
- }
- /**================================================================
- ================================================================*/
- int32_t app_flag_write(uint32_t data ,uint32_t start_add)
- {
- FLASH_Unlock();
- //
- FLASH_EraseOnePage(start_add); //写之前先擦一遍,每次擦2K
- if (FLASH_COMPL != FLASH_ProgramWord(start_add, data)) //写
- {
- FLASH_Lock();
- //printf("flash write fail! \r\n");
- return 1;
- }
- FLASH_Lock();
- return 0;
- }
- /**================================================================
- ================================================================*/
- #define FLASH_PAGE_SIZE 2048
- /**
- * [url=home.php?mod=space&uid=247401]@brief[/url]
- * @param void
- * @return
- * - `SUCCESS: 表示操作成功
- * - 其它值表示出错
- */
- int32_t app_flash_write(uint32_t *data ,uint32_t Flash_address)
- {
- uint32_t i;
- uint32_t start_add;
- start_add = Flash_address;
-
- FLASH_Unlock();
- //
- for(i = 0;i<FLASH_PAGE_SIZE/FLASH_PAGE_SIZE;i++)
- {
- FLASH_EraseOnePage(start_add+i*FLASH_PAGE_SIZE); //写之前先擦一遍,每次擦2K
- }
- //
- for(i=0;i<FLASH_PAGE_SIZE/4 ;i++)
- {
- if (FLASH_COMPL != FLASH_ProgramWord(start_add+i*4, data[i])) //写
- {
- FLASH_Lock();
- //printf("flash write fail! \r\n");
- receive_app_done = 0;
- return 1;
- }
- }
- FLASH_Lock();
- return 0;
- }
- /**================================================================
- //升级APP
- ================================================================*/
- void IAP_UPDATE_APP(void)
- {
- ready_write_addr = FLASH_APP_BASE_ADDR + pages_number*2048;
- //
- while(app_flash_write((uint32_t *)flash_buf , ready_write_addr)); //IAP每次升级2K
- //
- memset(flash_buf,0x00,2048);
- pages_number++;
- }
编写完成后,我们回到 main.c 中完成逻辑部分:
main.c
- #include "main.h"
- #include "SD_Driver.h"
- #include "iap.h"
- // 文件盘符
- #define DRIVE_LETTER "1:"
- #define FLASH_PAGE_SIZE 2048
- __IO uint32_t count_time = 0;
- void IAP_Upgrade(char *path);
- extern int32_t app_flash_write(uint32_t *data ,uint32_t Flash_address);
- /**================================================================
- 读取Flash
- ================================================================*/
- uint32_t FLASH_ReadWord(uint32_t address)
- {
- return *(__IO uint32_t*)address;
- }
- // 初始化PB5做为升级指示
- void LED_Init(void);
- #define LED_ON GPIO_SetBits(GPIOB,GPIO_PIN_5)
- #define LED_OFF GPIO_ResetBits(GPIOB,GPIO_PIN_5)
- int main(void)
- {
- // 检查标志位,判断有没有app,如果有,直接跳转到app中
- if(FLASH_ReadWord(app_update_flag_addr) == 0x12345678)
- {
- // 跳转前设置中断向量表
- SCB->VTOR = FLASH_START_ADDR;
- // 跳转到APP起始地址,期间不能被其他中断打断,否则会跳转失败
- iap_load_app(FLASH_START_ADDR);
-
- return 0;
- }
-
- // 没有app 则进行升级,升级请将固件拷到SD卡中,插入到卡槽中
-
- log_init();
- LED_Init();
- printf("\n");
- printf("Start running Bootloader.\n");
-
-
- // 传入要升级的文件名称
- IAP_Upgrade("project.bin");
-
-
- while(1)
- {
- // 升级失败,PB5快闪
- systick_delay_ms(100);
- systick_delay_ms(100);
- if(GPIO_ReadOutputDataBit(GPIOB,GPIO_PIN_5) == RESET)
- {
- LED_ON;
- }else{
- LED_OFF;
- }
- }
- }
- void LED_Init(void)
- {
- RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOB,ENABLE);
-
- GPIO_InitType GPIO_InitStrucetre;
- GPIO_InitStruct(&GPIO_InitStrucetre);
-
- GPIO_InitStrucetre.GPIO_Mode = GPIO_Mode_Out_PP;
- GPIO_InitStrucetre.Pin = GPIO_PIN_5;
- GPIO_InitPeripheral(GPIOB,&GPIO_InitStrucetre);
- }
- void IAP_Upgrade(char *path)
- {
- char _path[50]={0};
- // 判断是否有盘符,没有则加上盘符
- if(strncmp(path,DRIVE_LETTER,strlen(DRIVE_LETTER)) != 0)
- {
- sprintf(_path,"%s%s",DRIVE_LETTER,path);
- }else{
- strcpy(_path,path);
- }
-
-
- FATFS *fs;
- fs = malloc(sizeof (FATFS)); /* Get work area for the volume */
- FRESULT res=f_mount(fs,DRIVE_LETTER,1); //挂载
-
- if(res == FR_OK)
- {
- printf("SD Mount Ok.\n");
-
- // 开始检查是否存在指定的文件
- FILINFO fno;
- FRESULT fr = f_stat(_path,&fno);
- if(fr != FR_OK)
- {
- printf("Upgrade file not found\n");
- return;
- }
-
- }else{
- printf("SD Not Mount.\n");
- return;
- }
-
- printf("Upgrade Start ... \n");
-
- LED_ON;
-
- // 写入Flash页码
- uint16_t pages_number = 0;
- // 读写Flash地址
- uint32_t ready_write_addr = 0;
- char buff[FLASH_PAGE_SIZE] ;
- UINT br = 0;
-
- FIL fil;
- FRESULT fr = f_open(&fil, _path, FA_READ);
- if(fr == FR_OK)
- {
- do{
- memset(buff,0,FLASH_PAGE_SIZE);
- f_read(&fil,buff,FLASH_PAGE_SIZE,&br);
- if(br > 0)
- {
- ready_write_addr = FLASH_APP_BASE_ADDR + pages_number*FLASH_PAGE_SIZE;
- while(app_flash_write((uint32_t *)buff , ready_write_addr)); //IAP每次升级2K
- pages_number++;
- }
- } while(br);
-
- f_close(&fil);
-
- if(pages_number > 0)
- {
- printf("Upgrade completed, start jumping to app.\n\n\n");
-
- // 升级完成操作 释放GPIOB
- GPIO_DeInit(GPIOB);
- // 写IAP升级标志
- app_flag_write(0x12345678 ,app_update_flag_addr);
-
- //NVIC_SystemReset(); // 复位
- // 跳转前设置中断向量表
- SCB->VTOR = FLASH_START_ADDR;
- // 跳转到APP起始地址,期间不能被其他中断打断,否则会跳转失败
- iap_load_app(FLASH_START_ADDR);
- }
- }else{
- printf("File opening failed\n");
- }
- }
以上程序编写完成后可以直接烧入开发板中,因为此时没有拷入升级文件,所以PB5灯应该会快闪才对,因为没升级文件无法升级。
2)APP(业务逻辑)程序编写
Flash总大小 0x80000 被Bootloader占用了16K 就剩下了 0x7C000,内存总大小 0x24000,剩余就是0x20000,并且之前空间已经被占用,所以新的超始地址就需要向后偏移相应的大小,以下就是App的配置:
app keil5配置
我们可以看到IROM1的起始地址变成了 0x8004000,就是0x8000000+0x4000(Bootloader的大小),大小要减去16K变成了 0x7C000,就是0x80000-0x4000,IRAM1也是一样的道理。
写app程序之前,我们要先了解一个知识点:中断向量表。
什么是中断向量表?
中断向量表就是中断向量的列表。
中断向量表在内存中保存,其中存放着中断源(中断向量号或者中断类型号)所对应的中断处理程序的入口地址。
一个中断源对应一个中断处理程序,这种关系索引表,就是中断向量表。
提到这个的原因就是因为我们app开始时的中断向量表地址是 0x08000000,而我们app程序整体向后移了16K,如果我们app程序中有用到中断功能,哪么如果还是指向之前的中断向量表的地址,就会无法执行或直接卡死,因为这个地址已经变了,但我们没有去更新。在写Bootloader程序时,大家应该也注意到了跳转app前设置中断向量表,虽然这里设置了,但只能影响跳转时的,在进入APP又会变成无效了,这是为什么呢?
因为啊,在进入程序前,会有个 SystemInit() 初始化系统时钟,而这里又设置了中断向量表,导致我们跳转前设置的又变成了无效。不信的话,大家可以编写一个简单的定时器程序,做一个更新中断,在初始化定时前和之后都打印一条消息,我们会发现,一定初始化定时器程序后,立马就卡死了,就是因为此地址不对。
初始化时钟的程序在 system_n32g45x.c 文件中,在此文件里找到 void SystemInit(void) 函数,在函数末尾我们可以看到这样一个配置(红框中):
可以看到这里又重置了中断向量表的地址,FLASH_BASE 是Flash的起始地址,也就是 0x08000000 再与上了一个偏移地址 VECT_TAB_OFFSET ,我们点右键跳转到他的定义,就会发现 VECT_TAB_OFFSET 配置的地址是 0x00,所以才会出现这样的原因,此时我只需要把 VECT_TAB_OFFSET 的值改为 0x4000,就可以了,就像这样(注意红框中值变化):
需要注意的地方已经讲完了,剩余的就是正常编写程序就可以了,我这里为了方便演示,我就做了一个点灯和定时器,正常运行APP后,我们点亮板载的PA8,并闪烁,初始化CountTime 1ms更新一次的定时器,并且每隔500ms打印一条消息。APP部分也是基于之前的SD卡程序演示文件的读写并打印消息,方便观察运行情况。为了在进入app后也能升级,我们添加了一个按键,使用的就是 开发板上的WAKEUP按键,当我们将有固件的TF卡插入后,我们点击这个按键就会开始升级。
注意:这里为了简单点,把固件名称固定为 “ Project.bin ”,Bootloader就是读取这个文件就可以升级。升级现象就是:当按下WAKEUP按键后,PB5灯先亮起,开始升级,升级完成后会熄灭,之后跳转到APP中,PA8 亮起并闪烁,整个升级过程完成。
好了,打开main.c 进行开始编写代码:
main.c
- #include "main.h"
- #include "SD_Driver.h"
- #include "iap.h"
- extern int32_t app_flash_write(uint32_t *data ,uint32_t Flash_address);
- void SD_Test(void);
- // 按键软件防抖配置
- #define KEY_LOCK_TIME 50
- uint32_t KeyPressTime = 0;
- // 按键初始化
- void Key_Init(void);
- // LED初始化
- void LED_Init(void);
- #define LED_ON GPIO_SetBits(GPIOA,GPIO_PIN_8)
- #define LED_OFF GPIO_ResetBits(GPIOA,GPIO_PIN_8)
- int main(void)
- {
- log_init();
- LED_Init();
- CountDown_Init();
- Key_Init();
-
- printf("App Init Ok.\n");
-
- LED_ON;
-
- // SD卡测试
- SD_Test();
-
- uint32_t t = count_time;
- while(1)
- {
- if(count_time - t>= 500)
- {
- t = count_time;
-
- printf("Test Msg, CountTime=%d\n",count_time);
-
- if(GPIO_ReadOutputDataBit(GPIOA,GPIO_PIN_8) == RESET)
- {
- LED_ON;
- }else{
- LED_OFF;
- }
- }
-
- // USART接收数据打印
- if(USART_Recv_Flag)
- {
- printf("Recv: %s\n",USART_RecvBuff);
-
- USART_Recv_Flag = 0;
- USART_Recv_Size = 0;
- memset(USART_RecvBuff,0,USART_MAX_SIZE);
- }
- }
- }
- void SD_Test(void)
- {
-
- FATFS *fs;
- fs = malloc(sizeof(FATFS));
- FRESULT fr = f_mount(
- fs, // FATFS对象
- "1:", // 挂载的盘符 相当于win系统下的 C:
- 1 // 挂载选项 0 延迟挂载 1立即挂载
- );
- if(fr == FR_OK)
- {
- printf("挂载SD成功.\n");
- }else if(fr == FR_NO_FILESYSTEM)
- {
- // 没有文件系统
- printf("没有文件系统,开始格式化 ...\n");
-
- BYTE work[FF_MAX_SS]; /* Work area (larger is better for processing time) */
- fr = f_mkfs("1:", 0, work, sizeof work);
- printf("格式化结果: %s %d\r\n", fr==FR_OK?"成功":"失败",fr);
- }else{
- printf("SD卡挂载失败.\n");
- return;
- }
-
- // 开始读写文件测试
- char path[] = "1:test.txt"; // 同样1:是盘符
- char data[] = "Write Data Content.";
-
- // 写入文件测试
- FIL fil;
- fr = f_open(&fil,path,FA_CREATE_ALWAYS | FA_WRITE);
- if(fr == FR_OK)
- {
- UINT br = 0;
- f_write(&fil,data,sizeof(data),&br);
- f_close(&fil);
-
- printf("创建文件成功,写入字节数: %d\n",br);
- }else{
- printf("创建文件失败\n");
- }
-
- // 读取文件测试,读取刚写入的文件内容
- fr = f_open(&fil,path,FA_READ);
- if(fr == FR_OK)
- {
- UINT br = 0;
- char tmp[100]={0};
-
- f_read(&fil,tmp,100,&br);
- f_close(&fil);
-
- printf("读取文件成功,文件内容: %s\n",tmp);
- }else{
- printf("打开文件失败\n");
- }
-
- // 删除文件
- fr = f_unlink(path);
- printf("删除文件结果: %s\n",fr == FR_OK?"成功":"失败");
-
- }
- // 按键初始化
- void Key_Init(void)
- {
- RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA,ENABLE);
-
- GPIO_InitType KeyGpio_InitStruct;
- GPIO_InitStruct(&KeyGpio_InitStruct);
-
- KeyGpio_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
- KeyGpio_InitStruct.Pin = GPIO_PIN_0;
- GPIO_InitPeripheral(GPIOA,&KeyGpio_InitStruct);
-
- // 中断线配置
- GPIO_ConfigEXTILine(GPIOA_PORT_SOURCE,GPIO_PIN_SOURCE0);
-
- // 按键中断触发方式
- EXTI_InitType Exti_InitStruct;
- Exti_InitStruct.EXTI_Line = EXTI_LINE0;
- Exti_InitStruct.EXTI_LineCmd = ENABLE;
- Exti_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
- Exti_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
- EXTI_InitPeripheral(&Exti_InitStruct);
-
- // 配置中断
- NVIC_InitType NVIC_InitStructure;
- NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x05;
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
- NVIC_Init(&NVIC_InitStructure);
-
- }
- void EXTI0_IRQHandler(void)
- {
- if(EXTI_GetITStatus(EXTI_LINE0) != RESET)
- {
- EXTI_ClrITPendBit(EXTI_LINE0);
-
- // 软件防抖
- if( GPIO_ReadInputDataBit(GPIOA,GPIO_PIN_0) != RESET || count_time - KeyPressTime <= KEY_LOCK_TIME)return;
- KeyPressTime = count_time;
-
- printf("Key Press\n");
-
- // 跳转前将中断向量表改回去
- SCB->VTOR = FLASH_BASE;
- // 清除升级标志
- app_flash_write(0x00,app_update_flag_addr);
- // 复位,开始升级
- NVIC_SystemReset();
- }
- }
- void LED_Init(void)
- {
- RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA,ENABLE);
-
- GPIO_InitType GPIO_InitStrucetre;
- GPIO_InitStruct(&GPIO_InitStrucetre);
-
- GPIO_InitStrucetre.GPIO_Mode = GPIO_Mode_Out_PP;
- GPIO_InitStrucetre.Pin = GPIO_PIN_8;
- GPIO_InitPeripheral(GPIOA,&GPIO_InitStrucetre);
-
- }
至此,本次项目已经全部完成了,并且附上完整实现代码,希望能给大家带来帮助。
最后一部分我也准备了真实应用实例,通过微信小程序来实现升级,有兴趣可以继续阅读下一部分内容。
四、使用微信小程序实现IAP升级
前言:微信是目前使用最广泛的通讯方式了,其推出的小程序也简单易用,如果升级能结合微信小程序来实现,则能极快的减少升级成本,仅需要把固件通过微信下发,客户就可以直接使用手机进行升级,这将是一个非常快捷且节约成本的升级方案了,再也不用千里迢迢跑到客户哪边去升级了。
所以,接下来做的就是实现微信小程序的升级功能,本次实例的方案使用的是 AT固件的蓝牙模块,这样移植起来非常简便,不过要求支持透传功能,下面介绍下使用到的蓝牙模块:
蓝牙模块:集芯微 G75-C2G4A12S3a
类型:AT固件,支持透传
价格:不到4元
优势:价格便宜,使用简单,容易购买到
缺点:AT固件只能发送字符型,无法发送二进制数据,因为遇到\0就会截断,该模块单包数据有限制,实测 96字节可以发,128字节模块直接死掉,应该是溢出了
说了模块,就是连接方式了:模块 开发板
VDD ---- 3V3
GND ---- GND
RXD ---- PB10
TXD ---- PB11
模块用到了几个AT命令
1、修改蓝牙名称:
为了便于标识,我们将蓝牙名称修改为 " IAP_N32G457 ",命令描述如下:
2、数据透传开启命令:
这里数据主要是乃至数据透传,这样可以方便进行数据的交互。开启透传后,连接上蓝牙的设备发送过来的数据会直接通过串口发给MCU,同样MCU通过串口发给蓝牙模块的数据会直接发送到连接上来的设备端。命令描述如下:
3、配置保存命令,用于将设置保存下来:
以上直接截图原文档中内容,文末我也会将此文档上传上来。
说完了模块的几条命令,下面就来配置一下与模块通讯的串口了,我们新建一个ble.h ble.c 来处理蓝牙相关的内容。
// ble.h
- #ifndef BLE_H_
- #define BLE_H_
- #include "main.h"
- // AT固件蓝牙串口配置
- #define BLE_USART_RCC RCC_APB1_PERIPH_USART3
- #define BLE_USARTx USART3
- #define BLE_GPIO_RCC RCC_APB2_PERIPH_GPIOB
- #define BLE_GPIO_PORT GPIOB
- #define BLE_PIN_TX GPIO_PIN_10
- #define BLE_PIN_RX GPIO_PIN_11
- #define BLE_IRQn USART3_IRQn
- #define BLE_IRQHandle USART3_IRQHandler
- // 定义蓝牙收到消息状态
- typedef struct {
- char cmd[18];
- char content[100];
- char status[10];
- } BleResultStatus;
- // 蓝牙名称
- #define BLE_NAME "IAP_N32G457"
- // 蓝牙每次最大接收数
- #define BLE_MAX_RECV_SIZE 1024
- // 蓝牙接收缓存区
- extern char BLE_RecvBuff[BLE_MAX_RECV_SIZE];
- // 蓝牙接收数据大小
- extern uint16_t BLE_Recv_Size;
- // 蓝牙接收完成标志
- extern uint8_t BLE_Recv_Flag;
- // 连接状态
- extern uint8_t BLE_Connected ;
- void BLE_Init(void);
- // 发送数据
- void BLE_SendData(const char *data,uint16_t size);
- uint8_t BleSendCommand(char *cmd, BleResultStatus *result);
- uint8_t BleRestoreStatus(char *data, BleResultStatus *result);
- void BLE_Clear_Flag(void);
- #endif
// ble.c
- #include "ble.h"
- // 蓝牙接收缓存区
- char BLE_RecvBuff[BLE_MAX_RECV_SIZE] = {0};
- // 蓝牙接收数据大小
- uint16_t BLE_Recv_Size = 0;
- // 蓝牙接收完成标志
- uint8_t BLE_Recv_Flag = 0;
- // 连接状态
- uint8_t BLE_Connected = 0;
- void BLE_Init(void)
- {
-
- // 配置中断
- NVIC_InitType NVIC_InitStructure;
- /* Enable the USARTz Interrupt */
- NVIC_InitStructure.NVIC_IRQChannel = BLE_IRQn;
- //NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
- NVIC_Init(&NVIC_InitStructure);
-
-
- if(BLE_USART_RCC != RCC_APB2_PERIPH_USART1 && BLE_USART_RCC != RCC_APB2_PERIPH_UART6 && BLE_USART_RCC != RCC_APB2_PERIPH_UART6 )
- {
- RCC_EnableAPB1PeriphClk(BLE_USART_RCC,ENABLE);
- }else{
- RCC_EnableAPB2PeriphClk(BLE_USART_RCC | RCC_APB2_PERIPH_AFIO,ENABLE);
- }
-
- RCC_EnableAPB2PeriphClk(BLE_GPIO_RCC,ENABLE);
-
-
- GPIO_InitType GPIO_InitStructure;
- USART_InitType USART_InitStructure;
- GPIO_InitStructure.Pin = BLE_PIN_TX;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitPeripheral(BLE_GPIO_PORT, &GPIO_InitStructure);
- GPIO_InitStructure.Pin = BLE_PIN_RX;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
- GPIO_InitPeripheral(BLE_GPIO_PORT, &GPIO_InitStructure);
- USART_InitStructure.BaudRate = 115200;
- USART_InitStructure.WordLength = USART_WL_8B;
- USART_InitStructure.StopBits = USART_STPB_1;
- USART_InitStructure.Parity = USART_PE_NO;
- USART_InitStructure.HardwareFlowControl = USART_HFCTRL_NONE;
- USART_InitStructure.Mode = USART_MODE_TX | USART_MODE_RX;
- // init uart
- USART_Init(BLE_USARTx, &USART_InitStructure);
-
- // 配置接收中断
- USART_ConfigInt(BLE_USARTx,USART_INT_RXDNE,ENABLE);
- // 配置空闲中断
- USART_ConfigInt(BLE_USARTx,USART_INT_IDLEF,ENABLE);
- // enable uart
- USART_Enable(BLE_USARTx, ENABLE);
-
- BleResultStatus result;
- // 取消透传
- BleSendCommand("AT+BT_TRANS=0\n\r",&result);
- systick_delay_ms(10);
- // 获取蓝牙名称
- BleSendCommand("AT+BT_NAME?\n\r",&result);
- if(strcmp(result.content,BLE_NAME)==0)
- {
- printf("蓝牙名称正确无需修改\n");
- }else{
- printf("蓝牙名称不正确,当前蓝牙名称: %s\n",result.content);
- // 开始修改蓝牙名称
- char buff[32]={0};
- sprintf(buff,"AT+BT_NAME=%s\n\r",BLE_NAME);
- BleSendCommand(buff,&result);
- printf("蓝牙名称修改结果: %s\n",result.status);
- if(strcmp(toupper(result.status),"OK") == 0)
- {
- // 保存修改结果
- BleSendCommand("AT+UT_CFGSV\r\n",&result);
- printf("蓝牙保存结果: %s\n",result.status);
- }
-
- }
- systick_delay_ms(10);
- // 开启透传
- BleSendCommand("AT+BT_TRANS=1\n\r",&result);
- }
- void BLE_IRQHandle(void)
- {
- if (USART_GetIntStatus(BLE_USARTx, USART_INT_RXDNE) != RESET)
- {
- BLE_RecvBuff[BLE_Recv_Size++] = USART_ReceiveData(BLE_USARTx);
- }
-
- if (USART_GetIntStatus(BLE_USARTx, USART_INT_IDLEF) != RESET)
- {
- USART_ReceiveData(BLE_USARTx);
- BLE_RecvBuff[BLE_Recv_Size++] = '\0';
-
- BLE_Recv_Flag = 1;
-
- // 检查是否连接消息,如果是则开启透传模式
- if(strstr(BLE_RecvBuff,"+BT_CONN"))
- {
- BLE_Clear_Flag();
- BleSendCommand("AT+BT_TRANS=1\n\r",NULL);
- BLE_Connected = 1;
- }else if(strstr(BLE_RecvBuff,"+DISCONN")){
- BLE_Connected = 0;
- }
- }
- }
- // 发送数据
- void BLE_SendData(const char *data,uint16_t size)
- {
- for(int i=0;i<size;i++)
- {
- USART_SendData(BLE_USARTx, (uint8_t)data[i]);
- while(USART_GetFlagStatus(BLE_USARTx, USART_FLAG_TXDE) == RESET);
- }
- }
- void BLE_Clear_Flag(void)
- {
- BLE_Recv_Flag = 0;
- BLE_Recv_Size = 0;
- memset(BLE_RecvBuff,0,BLE_MAX_RECV_SIZE);
- }
- // 发送配置命令
- uint8_t BleSendCommand(char *cmd, BleResultStatus *result)
- {
- if(result != NULL)memset(result,0,sizeof(BleResultStatus));
-
- char _command[30]={0};
- if(strstr(cmd,"\r\n")){
- strcpy(_command,cmd);
- }else{
- strcpy(_command,cmd);
- strcat(_command,"\r\n");
- }
-
- // 发送命令
- BLE_SendData(_command,strlen(_command));
- if(result == NULL)return 1;
-
- // 等待获取返回信息
- uint32_t consume_time = count_time;
- while(!BLE_Recv_Flag && count_time-consume_time<500);
- if(!BLE_Recv_Flag)return 0;
-
-
- // 解析返回值信息
- uint8_t status=BleRestoreStatus(BLE_RecvBuff,result);
- // 清除接收状态
- BLE_Clear_Flag();
- return status;
- }
- // 解析命令状态
- uint8_t BleRestoreStatus(char *data, BleResultStatus *result)
- {
- if(data[0] == 0x0a){
- //printf("有换行符\n");
- strcpy(data ,data+1);
- }
-
-
- char seq[3] = "\r\n";
- char *token = strtok(data+1,seq);
- if(token == NULL)return 0;
-
- char tmp[60]={0};
- while(token != NULL)
- {
- if(strstr(token,"+")){
- strcpy(tmp,token);
- }else if(strlen(token)>1){
- strcpy(result->status,token);
- }
- token = strtok(NULL, seq);
- }
-
- memset(seq,0,3);
- strcpy(seq,":");
-
- token = strtok(tmp,seq);
- while(token != NULL){
- if(strstr(token,"+")){
- strcpy(result->cmd,token);
- }else if(strlen(token)>0){
- strcpy(result->content,token);
- }
- token = strtok(NULL, seq);
- }
-
- return 1;
- }
接下来就是处理蓝牙消息了
- #define PACK_SUB_SIZE 512
- // 包临时数据缓存区
- char Pack_Buff[1024];
- // 固件分包块数量
- uint16_t Bin_Sector = 0;
- // 分包接收到的固件大小
- uint16_t Bin_CurrSize = 0;
- // 固件大小
- uint16_t Bin_Size = 0;
- // 包大小
- uint16_t Pack_Size = 0;
- uint16_t Pack_Recv_Size = 0;
- // 分包接收状态 0 等待 1 正在接收
- uint8_t Pack_Recv_Flag = 0;
- // 固件名称
- #define BIN_NAME "1:Project.bin"
- // 固件缓存名称
- #define BIN_CACHE_NAME "1:tmp.bin"
- FIL bin_fil;
- // 蓝牙数据处理
- void BLE_DataDisponse(void)
- {
- if(!BLE_Recv_Flag)return;
-
- char buff[30]={0};
-
- if(strstr(BLE_RecvBuff,"UPGRADE")){
- // 升级请求
- if(mountSD() != FR_OK)
- {
- printf("挂载SD失败\n");
- strcpy(buff,"UPGRADE_ERROR");
- BLE_SendData(buff,strlen(buff));
- goto end_label;
- }
-
- //
- f_unlink(BIN_CACHE_NAME);
- // 以新建方式打开文件
- FRESULT fr = f_open(&bin_fil,BIN_CACHE_NAME,FA_CREATE_ALWAYS | FA_WRITE);
- if(fr != FR_OK)
- {
- printf("创建文件失败\n");
- strcpy(buff,"UPGRADE_ERROR");
- BLE_SendData(buff,strlen(buff));
-
-
- goto end_label;
- }
-
- strcpy(buff,"READY");
- BLE_SendData(buff,strlen(buff));
- printf("UPGRADE READY\n");
-
- }else if(strstr(BLE_RecvBuff,"LENTEST:")){
-
- // 确定每包大小
- sprintf(buff,"LEN:%d",strlen(BLE_RecvBuff));
- BLE_SendData(buff,strlen(buff));
-
- }else if(strstr(BLE_RecvBuff,"BIN:")){
-
- // 获取bin文件大小
- Bin_Size = atoi((strchr(BLE_RecvBuff,':')+1));
- printf("Bin Size=%d\n",Bin_Size);
-
- }else if(strstr(BLE_RecvBuff,"PACK:")){
-
- // 获取包大小
- Pack_Size = atoi((strchr(BLE_RecvBuff,':')+1));
- memset(Pack_Buff,0,sizeof(Pack_Buff));
- Pack_Recv_Size = 0;
- Pack_Recv_Flag =1;
- printf("Pack Size=%d\n",Pack_Size);
-
- }else if(strstr(BLE_RecvBuff,"BIN_END:")){
- f_close(&bin_fil);
- strcpy(buff,"START_UPGRADE");
- BLE_SendData(buff,strlen(buff));
-
- // 等待100ms后开始升级
- systick_delay_ms(100);
- printf("固件接收完成,开始升级\n");
-
- // 删除原固件文件
- f_unlink(BIN_NAME);
- // 将缓存重命名
- f_rename(BIN_CACHE_NAME,BIN_NAME);
-
- systick_delay_ms(100);
-
- USART_DeInit(BLE_USARTx);
- USART_ClrIntPendingBit(BLE_USARTx,USART_INT_RXDNE);
- USART_ClrIntPendingBit(BLE_USARTx,USART_INT_IDLEF);
-
- // 跳转前将中断向量表改回去
- SCB->VTOR = FLASH_BASE;
- // 清除升级标志
- app_flash_write(0x00,app_update_flag_addr);
-
- //关闭所有中断,防止重启事件被打断
- __set_FAULTMASK(1);
- // 复位,开始升级
- NVIC_SystemReset();
-
- }else if(Pack_Recv_Flag){
- // 开始接收数据
- strcat(Pack_Buff,BLE_RecvBuff);
- if(strlen(Pack_Buff) >= Pack_Size)
- {
- // 本组包接收完成,开始解包
- char pack[1024] = {0};
- uint16_t len = base64_decode(Pack_Buff,strlen(Pack_Buff),pack);
- printf("Recv Packlen=%d BinLen:%d\n",strlen(Pack_Buff),len);
- // 打印接收解码后的数据
- // printf("Pack: \n");
- // for(uint16_t i=0;i<512;i++)
- // {
- // printf("%02X ",pack[i]);
- // if((i+1)%32 == 0)printf("\n");
- // }
- // printf("\n");
-
- if(len>512)len=512;
- // 写入到SD卡虽
- UINT br=0;
- f_write(&bin_fil,pack,len,&br);
- Bin_Sector++;
- Pack_Recv_Size+=len;
-
- // 一组分包处理完成,开始重置状态
- Pack_Recv_Flag = 0;
- Pack_Size = 0;
- memset(Pack_Buff,0,sizeof(Pack_Buff));
- //printf("分包接收完成\n");
- }
- //
- BLE_Clear_Flag();
- // 回复本次接收的长度
- sprintf(buff,"RECV:%d",strlen(BLE_RecvBuff));
- BLE_SendData(buff,strlen(buff));
- return;
-
- }
-
- end_label:
- printf("BLE Recv: %s\n",BLE_RecvBuff);
- BLE_Clear_Flag();
- }
微信小程序蓝牙升级流程:
1、微信小程序连上设备后,发送 UPGRADE 命令,请求升级
2、设备端收到 UPGRADE 命令开始挂载 SD 卡,并做升级前检查,准备就绪后回复命令 READY ,表示准备就绪
3、小程序收到 READY 命令后,开始构建一个长数据包(通常为128位),并以 LENTEST: 开头,测试单包最大支持的发送长度
4、设备端收到 LENTEST: 命令后,回复收到长度,格式 LEN:x,x为实际收到的长度
5、小程序端收到 LEN:x 后确认单包最大可以发送的数据包大小,开始读取固件并按512字节每块进行读取,数据使用base64编码成文本格式数据,
先发送bin文件总大小和每块数据发送前都会发送组包文件大小,格式为:
1)发送bin文件大小格式为 BIN:x,x为bin大小
2)每块数据编码完成后发送分包大小命令 PACK:x, x为本次分包编码后文件大小
6、设备端收到 BIN:x 后开始清空缓存区,准备接收数据
7、小程序开始发送数据,设备端每次接收完每块拆分数据后会回复本次分包大小,格式为 RECV:x ,x为接收到的长度,并将收到的数据存到缓存中
8、设备端每完整接收完一块编码数据(即每块 512字节编码后的文本内容)后,会解码成二进制数据存入到SD卡中,直到接收完成为止
9、小程序端在固件全部发送完成后,会发送命令 BIN_END:CRC32 命令 (CRC32内容目前为空),表示本次发送结束。设备端接收到完成命令后,检验完成后开始设备升级。
最后附上小程序码:
小程序使用说明:因为每个蓝牙的服务ID都是不一样的,而使用到的模块是能用的模块,不可能仅我使用,所我的这个小程序是通过蓝牙的服务ID和蓝牙名称双重编定确认为可管理的设备的是,蓝牙的服务ID为 6E400001-B5A3-F393-E0A9-E50E24DCCA9E ,蓝牙名称需要以 IAP_ 开始,如果大家能修改自己的服务ID和名称可以尝试下。
整个升级流程完成,此部分内容截图不太好展示,所以后面将以视频方式呈现。
五、总结分享:
终于完成了整个项目,从开始到完成还是花了大力气的,中间也遇到了许多困难,也得到了一些热心群友的帮助,在此非常感谢。 说下遇到的问题:
1、最开始遇到了就是SD卡驱动和FATFS文件系统方面,因为完全不知道从哪开始,不知道要做些什么,还有就是开始选型,SD卡驱动到底是用SPI模式还是SDIO,毕竟N32G457是支持SDIO的,但最终选择SPI,主要是考虑到后面我在用其它款MCU时不用再费劲了,只要改下SPI驱动部分就可以很愉快的移植了,速度虽然会慢一些,但暂时应该不会做视频存储方面,应该能达到使用需求了,其实说到底还是想偷下懒了
。
2、就是中断向量表的问题,就是跳到APP中时已经设置了,但进入程序后一旦开启中断,就会卡死的问题,因为升级部分也是参考官方的示例,连官方示例都没有修改APP中中断向量值,所以就一直以为跳转时设置了就可以了,没想到在进入APP后,在 SystemInit() 函数中又把中断向量表改回去了,这个后面得到群友的提醒才明白,原来不是我设置的不对,而是又被改了
。不过在官方示例中是有说到,但在实例中Bootloader中有做,但APP中去没有处理,走了不少弯路。
IAP_SD卡升级.zip
(3.61 MB, 下载次数: 66)
微信小程序实现IAP升级.zip
(3.61 MB, 下载次数: 42)
G75蓝牙模块-AT指令集-v1.0.0.pdf
(423.09 KB, 下载次数: 30)