【STM32】HAL库 SPI DMA UART驱动开发

零、瞎BB一些

最近真的是.....过得...些许艰难...

从实习到正式入职,在公司的项目组待了3个月左右了,同事、项目、代码、逻辑、架构都熟悉了,毕业后入职1个月的时间里,能给项目组改了几个bug,能接小小的需求写代码,测试,联调,上线功能,正在自我感觉良好一切都渐渐适应,每天干活也开心得飞起的时候,leader通知我换部门....

来了新部门,一切都变了,项目不一样,代码的架构完全变了,之前项目组的代码庞大,是基于rtos的消息通知机制的架构,所以我直接关注到业务逻辑;而新的项目组的代码,没上rtos,是个逻辑代码,业务代码就是HAL库的代码,很底层,就是使用HAL库的spi、dma、tim、usart等等驱动直接些业务代码,而这些我都掌握的不太好,这太底层了。新项目中还用到的六轴传感器,这也是一个大坑,我的新导师都说这个东西很难,我看了几天六轴的相关资料、文档、驱动算法、正点原子的开源飞行器的书籍和代码,真的是...一头雾水....

新部门的人....也不熟悉,我自己又是孤僻的一批.... 感觉也没人爱搭理我...

不过,唯一的好处就是:这些又带来了新的挑战!之前的项目组我惊叹于FreeRTOS系统的魅力,系统架构在RTOS上样子,基于消息通知机制的实时任务系统是这样的结构,并且业务代码也相当的复杂。而现在的项目,更接近底层,我正好能学一学嵌入式的基础,STM32底层的驱动开发,这些太基础了,是一个学生在校期间就英爱学会了,而我是来了公司才接触嵌入式,底层的也就大概的看了看就去看项目代码了,项目代码中将底层的驱动层层封装,根本就没机会看到底层接口,上层接口的业务逻辑就够喝一壶了。

这次这个机会,正好!

 

STM32就是学习各类基础知识,原理啊,通讯协议啊之类的。

各种外设初始化也是走那个流程:使能时钟、配置句柄(xxx_HandleTpyeDef)、配置硬件底层相关(HAL_xxx_MspInit),用HAL库函数初始化句柄(其实就是讲配置好的内容写进寄存器,因为HAL库本来就是对寄存器的上层的封装)。

这里多加一个HAL_xxx_MspDeInit,项目代码里很多DeInit函数,其实就是【失能】掉一个外设的函数,因为初始化的函数相当于要用一个外设就要使能,等不用的时候就要失能。

各类外设要先掌握各原理,什么通信协议,那些总线(原理图)

比如SPI:通讯时就使用3条总线和一条片选信号线:SCK MOSI MISO NSS

UART的话比较简单:就是TX RX 两条通信时的引脚

DMA的话,有很多通道,要去芯片手册中看DMA的哪个通道能连接哪个外设,叫【DMA请求映像表】,DMA没什么引脚要配置,使用时就注意结构体的成员的配置:什么传输方向之类,最后配置完用__HAL_LINKDMA() 将dma句柄和外设句柄连接起来,啧啧啧多形象的函数命令,多好理解。

 

感觉之前学STM走了些弯路,一直用标准外设库去写,没早点用一下HAL库,对HAL库的不熟悉,让我到了新项目组一直学习学习看书看书,没有参与项目。而且师兄说标准外设库都快淘汰了...

代码也粘一下,毕竟都是自己手把手敲的,虽然....都是野火的教程里的代码。

一、UART

#ifndef BSP_USART_H
#define BSP_USART_H
​
#include "stm32f1xx.h"
#include <stdio.h>
​
//串口波特率
#define DEBUG_USART_BAUDRATE                    115200
​
//引脚定义
/*******************************************************/
#define DEBUG_USART                             USART1
#define DEBUG_USART_CLK_ENABLE()                __HAL_RCC_USART1_CLK_ENABLE()
​
#define DEBUG_USART_RX_GPIO_PORT                GPIOA
#define DEBUG_USART_RX_GPIO_CLK_ENABLE()        __HAL_RCC_GPIOA_CLK_ENABLE()
#define DEBUG_USART_RX_PIN                      GPIO_PIN_10
​
#define DEBUG_USART_TX_GPIO_PORT                GPIOA
#define DEBUG_USART_TX_GPIO_CLK_ENABLE()       __HAL_RCC_GPIOA_CLK_ENABLE()
#define DEBUG_USART_TX_PIN                      GPIO_PIN_9
​
#define DEBUG_USART_IRQHandler                  USART1_IRQHandler
#define DEBUG_USART_IRQ                         USART1_IRQn
​
​
void Usart_SendString(uint8_t *str);
void DEBUG_USART_Config(void);
int fputc(int ch, FILE *f);
int fgetc(FILE *f);
extern UART_HandleTypeDef UartHandle;
​
#endif
#include "bsp_usart.h"
​
//                 UartHandle管理串口所有配置
UART_HandleTypeDef UartHandle;
​
//配置与硬件底层无关内容:如串口协议,其中包括波特率,奇偶校验,停止位
void DEBUG_USART_Config()
{
    UartHandle.Instance = DEBUG_USART;
    
    //波特率,8位字长,1停止位,无奇偶校验,无硬件控制,收发模式
    UartHandle.Init.BaudRate = DEBUG_USART_BAUDRATE;
    UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
    UartHandle.Init.StopBits = UART_STOPBITS_1;//stm32f1xx_hal_uart.h
    UartHandle.Init.Parity = UART_PARITY_NONE;
    UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    UartHandle.Init.Mode = UART_MODE_TX_RX;
    
    HAL_UART_Init(&UartHandle);
    
    //使能串口接收中断
    __HAL_UART_ENABLE_IT(&UartHandle,UART_IT_RXNE);  
}
​
​
// mcu 底层硬件相关的配置如引脚、时钟、DMA、中断
//实际被HAL_UART_Init(stm32f1xx_hal_uart.c)该函数调用
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    GPIO_InitTypeDef GPIO_Init;
    
    //串口时钟 GPIO时钟
    DEBUG_USART_CLK_ENABLE();
    DEBUG_USART_RX_GPIO_CLK_ENABLE();
    DEBUG_USART_TX_GPIO_CLK_ENABLE();
    
    //配置引脚复用功能 TX 
    GPIO_Init.Pin = DEBUG_USART_TX_PIN;
    GPIO_Init.Mode = GPIO_MODE_AF_PP;
    GPIO_Init.Pull = GPIO_PULLUP;
    GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_Init);
    
    //配置Rx
    GPIO_Init.Pin = DEBUG_USART_RX_PIN;
    GPIO_Init.Mode = GPIO_MODE_AF_INPUT;//复用输入模式
    HAL_GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_Init); 
    
    HAL_NVIC_SetPriority(DEBUG_USART_IRQ ,0,1); //抢占优先级0,子优先级1
    HAL_NVIC_EnableIRQ(DEBUG_USART_IRQ );           //使能USART1中断通道 
    
}
​
/*****************  发送字符串 **********************/
/*  HAL_UART_Transmit 函数(这是一个阻塞的发送函数,无需重复判断串口是否发送完成)
    发送每个字符,直到遇到
    空字符才停止发送。*/
void Usart_SendString(uint8_t *str)
{
    unsigned int k=0;
  do 
  {
      HAL_UART_Transmit(&UartHandle,(uint8_t *)(str + k) ,1,1000);
      k++;
  } while(*(str + k)!='\0');
  
}
//重定向c库函数printf到串口DEBUG_USART,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
    /* 发送一个字节数据到串口DEBUG_USART */
    HAL_UART_Transmit(&UartHandle, (uint8_t *)&ch, 1, 1000);    
    
    return (ch);
}
​
//重定向c库函数scanf到串口DEBUG_USART,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{       
    int ch;
    HAL_UART_Receive(&UartHandle, (uint8_t *)&ch, 1, 1000); 
    return (ch);
}
//stm32f1xx_it.c  
//中断函数
​
void DEBUG_USART_IRQHandler(void)
{
    uint8_t ch = 1;
    if (__HAL_UART_GET_FLAG( &UartHandle, UART_FLAG_RXNE ) != RESET) 
    {
        ch=( uint16_t)READ_REG(UartHandle.Instance->DR);
        WRITE_REG ( UartHandle.Instance->DR,ch);
    }
}

 

二、DMA

dma直接读取存储器(RAM)的内容 给 usart,uart的tx接收到dma的数据后,tx是发送端,会将数据发给电脑,上位机可看到日志输出。

#ifndef BSP_DMA_H
#define BSP_DMA_H
​
#include "stm32f1xx.h"
​
//DMA
#define SENDBUFF_SIZE                           1000//发送的数据量
#define DEBUG_USART_DMA_CLK_ENABLE()            __HAL_RCC_DMA1_CLK_ENABLE()
#define DEBUG_USART_DMA_STREAM                  DMA1_Channel4
​
​
void USART_DMA_Config(void);
​
#endif

 

#include "bsp_dma.h"
​
//DMA 读取RAM中数据后 ,发送给usart(串口接收到数据打印出来)
​
uint8_t SendBuff[SENDBUFF_SIZE];
​
DMA_HandleTypeDef  DMA_Handle;      //DMA句柄
extern UART_HandleTypeDef UartHandle; //USRT句柄(定义在bsp_usart.c中)
​
void USART_DMA_Config(void)
{
    //1.使能时钟 2.配置句柄 3.HAL库函数初始化句柄 4.连接两个句柄
    __HAL_RCC_DMA1_CLK_ENABLE();
    
    DMA_Handle.Instance = DMA1_Channel4;//通道4(再数据手册中查看DMA的通道请求映像表)
    
    DMA_Handle.Init.Direction = DMA_MEMORY_TO_PERIPH; //存储器到外设
    DMA_Handle.Init.PeriphInc = DMA_PINC_DISABLE;     //外设非增量模式
    DMA_Handle.Init.MemInc = DMA_MINC_ENABLE;         //存储器增量模式
    DMA_Handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;    //外设数据长度:8位
    DMA_Handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;       //存储器数据长度:8位
    DMA_Handle.Init.Mode = DMA_NORMAL;                            //外设普通模式
    DMA_Handle.Init.Priority = DMA_PRIORITY_MEDIUM;               //中等优先级
    
    HAL_DMA_Init(&DMA_Handle);
    
    //连接DMA句柄
    __HAL_LINKDMA(&UartHandle,hdmatx,DMA_Handle);
​
}

 

三、SPI

spi野火的教程是用来读取Flash,这部分的调试 遇到了一些问题.... 代码调不通,读取不到flash的内容.....

#ifndef BSP_SPI_H
#define BSP_SPI_H
​
#include "stm32f1xx.h"
​
//FLASH相关
#define sFLASH_ID                        0XEF4017     //W25Q64
#define SPI_FLASH_PageSize              256
#define SPI_FLASH_PerWritePageSize      256
​
#define W25X_WriteEnable              0x06 
#define W25X_WriteDisable             0x04 
#define W25X_ReadStatusReg          0x05 
#define W25X_WriteStatusReg         0x01 
#define W25X_ReadData                   0x03 
#define W25X_FastReadData             0x0B 
#define W25X_FastReadDual             0x3B 
#define W25X_PageProgram              0x02 
#define W25X_BlockErase               0xD8 
#define W25X_SectorErase              0x20 
#define W25X_ChipErase                0xC7 
#define W25X_PowerDown                0xB9 
#define W25X_ReleasePowerDown       0xAB 
#define W25X_DeviceID                   0xAB 
#define W25X_ManufactDeviceID       0x90 
#define W25X_JedecDeviceID          0x9F
​
#define WIP_Flag                  0x01  /* Write In Progress (WIP) flag */
#define Dummy_Byte                0xFF
​
​
//spi1参数定义:spi号 和 GPIO等
#define     SPIx                            SPI1
#define     SPIx_CLK_ENABLE()               __HAL_RCC_SPI1_CLK_ENABLE()
#define     SPIx_SCK_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOA_CLK_ENABLE()
#define     SPIx_MISO_GPIO_CLK_ENABLE()     __HAL_RCC_GPIOA_CLK_ENABLE()
#define     SPIx_MOSI_GPIO_CLK_ENABLE()     __HAL_RCC_GPIOA_CLK_ENABLE()
#define     SPIx_CS_GPIO_CLK_ENABLE()       __HAL_RCC_GPIOC_CLK_ENABLE()
​
//强制或解除APB2外围设备复位
#define     SPIx_FORCE_RESET()      __HAL_RCC_SPI1_FORCE_RESET()
#define     SPIx_RELEASE_RESET()    __HAL_RCC_SPI1_RELEASE_RESET()
​
//SPI1引脚宏定义 SCK MISO  MOSI NSS(CS)
#define SPIx_SCK_PIN                     GPIO_PIN_5
#define SPIx_SCK_GPIO_PORT               GPIOA
​
#define SPIx_MISO_PIN                    GPIO_PIN_6
#define SPIx_MISO_GPIO_PORT              GPIOA
​
#define SPIx_MOSI_PIN                    GPIO_PIN_7
#define SPIx_MOSI_GPIO_PORT              GPIOA
​
#define FLASH_CS_PIN                     GPIO_PIN_0              
#define FLASH_CS_GPIO_PORT               GPIOC 
​
//控制 CS(NSS)引脚输出电平的宏(配置产生起始和停止信号)
#define digitalHi(p,i)              {p->BSRR=i;}              //设置为高电平      
#define digitalLo(p,i)              {p->BSRR=(uint32_t)i << 16;}                //输出低电平
#define SPI_FLASH_CS_LOW()      digitalLo(FLASH_CS_GPIO_PORT,FLASH_CS_PIN )
#define SPI_FLASH_CS_HIGH()     digitalHi(FLASH_CS_GPIO_PORT,FLASH_CS_PIN )
​
/*等待超时时间*/
#define SPIT_FLAG_TIMEOUT         ((uint32_t)0x1000)
#define SPIT_LONG_TIMEOUT         ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))
​
/*信息输出*/
#define FLASH_DEBUG_ON         1
​
#define FLASH_INFO(fmt,arg...)           printf("<<-FLASH-INFO->> "fmt"\n",##arg)
#define FLASH_ERROR(fmt,arg...)          printf("<<-FLASH-ERROR->> "fmt"\n",##arg)
#define FLASH_DEBUG(fmt,arg...)          do{\
                                          if(FLASH_DEBUG_ON)\
                                          printf("<<-FLASH-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
                                          }while(0)
​
/*************************************************************************************************************/
​
​
//初始化
void SPI_FLASH_Init(void);
​
//
void SPI_FLASH_SectorErase(uint32_t SectorAddr);
void SPI_FLASH_BulkErase(void);
void SPI_FLASH_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void SPI_FLASH_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead);
uint32_t SPI_FLASH_ReadID(void);
uint32_t SPI_FLASH_ReadDeviceID(void);
void SPI_FLASH_StartReadSequence(uint32_t ReadAddr);
void SPI_Flash_PowerDown(void);
void SPI_Flash_WAKEUP(void);
​
uint8_t SPI_FLASH_ReadByte(void);
uint8_t SPI_FLASH_SendByte(uint8_t byte);
uint16_t SPI_FLASH_SendHalfWord(uint16_t HalfWord);
void SPI_FLASH_WriteEnable(void);
void SPI_FLASH_WaitForWriteEnd(void);
​
#endif
#include "bsp_spi.h"
​
/*
初始化:  
    GPIO 结构体
    SPI 结构体
​
写应用接口:
    读flash 写flash 擦除...
*/
​
//SPI句柄结构体
SPI_HandleTypeDef HandleSPI;
​
static __IO uint32_t  SPITimeout = SPIT_LONG_TIMEOUT;
​
static uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);
​
​
​
//配置硬件资源
//初始化SPI GPIO部分引脚  GPIO 引脚模式初始化 复用功能(用户定义强函数)
void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi) 
{
    GPIO_InitTypeDef  GPIO_InitStruct;
    
    //使能GPIO 和SPI 时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_SPI1_CLK_ENABLE();
    
    //配置4个GPIO引脚 SCK MOSI MISO NSS
    GPIO_InitStruct.Pin       = SPIx_SCK_PIN;
    GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull      = GPIO_PULLUP;
    GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(SPIx_SCK_GPIO_PORT, &GPIO_InitStruct);
    
    GPIO_InitStruct.Pin = SPIx_MISO_PIN;
    HAL_GPIO_Init(SPIx_MISO_GPIO_PORT, &GPIO_InitStruct);
    
    GPIO_InitStruct.Pin = SPIx_MOSI_PIN;
    HAL_GPIO_Init(SPIx_MOSI_GPIO_PORT, &GPIO_InitStruct);
    
    GPIO_InitStruct.Pin = FLASH_CS_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    HAL_GPIO_Init(SPIx_MOSI_GPIO_PORT, &GPIO_InitStruct);//向寄存器写入参数(完成GPIO初始化)
    
}
​
//初始化SPI模式:
void SPI_FLASH_Init(void)
{
    HandleSPI.Instance = SPI1;
    // SPI 外设配置为主机端,双线全双工模式,数据帧长度为 8位,使用 SPI 模式 3(CLKPolarity =1,CLKPhase =1),NSS 引脚由软件控制以及 MSB 先行模式
    HandleSPI.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
    HandleSPI.Init.Direction = SPI_DIRECTION_2LINES;
    HandleSPI.Init.CLKPhase = SPI_PHASE_2EDGE;
    HandleSPI.Init.CLKPolarity = SPI_POLARITY_HIGH;
    HandleSPI.Init.CRCPolynomial = 7;
    HandleSPI.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    HandleSPI.Init.DataSize = SPI_DATASIZE_8BIT;
    HandleSPI.Init.FirstBit = SPI_FIRSTBIT_MSB;
    HandleSPI.Init.NSS = SPI_NSS_SOFT;
    HandleSPI.Init.TIMode = SPI_TIMODE_DISABLE;
    HandleSPI.Init.Mode = SPI_MODE_MASTER;//主模式
    
    //配置写进寄存器、使能SPI
    HAL_SPI_Init(&HandleSPI);
    __HAL_SPI_ENABLE(&HandleSPI);
}
​
​
​
​
/**
  * @brief  使用SPI发送一个字节的数据
  * @param  byte:要发送的数据
  * @retval 返回接收到的数据
  */
uint8_t SPI_FLASH_SendByte(uint8_t byte)
{
    SPITimeout = SPIT_FLAG_TIMEOUT;
    
    //等待发送缓冲区为空 txe事件(Tx buff empty)
    while(RESET ==  __HAL_SPI_GET_FLAG(&HandleSPI,SPI_FLAG_TXE))
    {
        if ((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);
    }
    
    //写入数据寄存器,把要写入的数据写入发送缓冲区
    WRITE_REG(HandleSPI.Instance->DR,byte);
    
    SPITimeout = SPIT_FLAG_TIMEOUT;
    
    //等待接收缓冲区非空 RXNE事件(Rx buffer empty flag)
    while(RESET == __HAL_SPI_GET_FLAG(&HandleSPI,SPI_FLAG_RXNE))
    {
        if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1);
    }
    
    //读取数据寄存区 获取接收缓冲区数据
    return READ_REG(HandleSPI.Instance->DR);
}
​
/**
  * @brief  使用SPI读取一个字节的数据
  * @param  无
  * @retval 返回接收到的数据
  */
uint8_t SPI_FLASH_ReadByte(void)
{
    return (SPI_FLASH_SendByte(Dummy_Byte));
}
​
 /**
  * @brief  读取FLASH Device ID
  * @param  无
  * @retval FLASH Device ID
  */
uint32_t SPI_FLASH_ReadDeviceID(void)
{
  uint32_t Temp = 0;
​
  /* Select the FLASH: Chip Select low */
  SPI_FLASH_CS_LOW();
​
  /* Send "RDID " instruction */
  SPI_FLASH_SendByte(W25X_DeviceID);
  printf("recv1 = %x\n",SPI_FLASH_SendByte(Dummy_Byte));
  printf("recv2 = %x\n",SPI_FLASH_SendByte(Dummy_Byte));
  printf("recv3 = %x\n",SPI_FLASH_SendByte(Dummy_Byte));
  
  /* Read a byte from the FLASH */
  Temp = SPI_FLASH_SendByte(Dummy_Byte);
    printf("recv4 = %x\n",Temp);
  /* Deselect the FLASH: Chip Select high */
  SPI_FLASH_CS_HIGH();
​
  return Temp;
}
​
/**
 * @brief 读取FLASH ID
 * @retval FLASH ID
*/
uint32_t SPI_FLASH_ReadID(void)
{
    uint32_t temp = 0,temp1 = 0,temp0 = 0,temp2 = 0;
    
    //开始通讯:CS低电平
    SPI_FLASH_CS_LOW();
    
    //发送JEDEC指令
    SPI_FLASH_SendByte(W25X_JedecDeviceID);
    
    //读取数据(结果)
    temp0 = SPI_FLASH_SendByte(Dummy_Byte);
    temp1 = SPI_FLASH_SendByte(Dummy_Byte);
    temp2 = SPI_FLASH_SendByte(Dummy_Byte);
    
    //停止通讯:CS高电平
    SPI_FLASH_CS_HIGH();
    
    //数据组合起来 返回
    temp = (temp0 << 16) | (temp1 << 8) | temp2;
    return temp;    
}
​
//发送 “写使能”给flash(之后可以对flash芯片存储矩阵写入数据)
void SPI_FLASH_WriteEnable()
{
    SPI_FLASH_CS_LOW();                     //开始通讯:CS低电平
    SPI_FLASH_SendByte(W25X_WriteEnable);   //发送xxx指令
    SPI_FLASH_CS_HIGH();                    //停止通讯:CS高电平
}
​
//WIP(busy)标志 flash内粗正在写入
#define WIP_Flag 0x01
/*
 *@brief 读取状态寄存器等待FLASH芯片空闲
 *
*/
void SPI_FLASH_WaitForWriteEnd(void)
{
    uint8_t FLASH_Status = 0;
    SPI_FLASH_CS_LOW();  
    SPI_FLASH_SendByte(W25X_ReadStatusReg);
    
    do //若flash忙 则等待
    {
        FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);
    }while(SET == (FLASH_Status & WIP_Flag));
    
    SPI_FLASH_CS_HIGH();  
}
​
 /**
  * @brief  擦除FLASH扇区
  * @param  SectorAddr:要擦除的扇区地址
  * @retval 无
  */
void SPI_FLASH_SectorErase(uint32_t SectorAddr)
{
  /* 发送FLASH写使能命令 */
  SPI_FLASH_WriteEnable();
  SPI_FLASH_WaitForWriteEnd();
  /* 擦除扇区 */
  /* 选择FLASH: CS低电平 */
  SPI_FLASH_CS_LOW();
  /* 发送扇区擦除指令*/
  SPI_FLASH_SendByte(W25X_SectorErase);
  /*发送擦除扇区地址的高位*/
  SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);
  /* 发送擦除扇区地址的中位 */
  SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);
  /* 发送擦除扇区地址的低位 */
  SPI_FLASH_SendByte(SectorAddr & 0xFF);
  /* 停止信号 FLASH: CS 高电平 */
  SPI_FLASH_CS_HIGH();
  /* 等待擦除完毕*/
  SPI_FLASH_WaitForWriteEnd();
}
​
 /**
  * @brief  擦除FLASH扇区,整片擦除
  * @param  无
  * @retval 无
  */
void SPI_FLASH_BulkErase(void)
{
  /* 发送FLASH写使能命令 */
  SPI_FLASH_WriteEnable();
​
  /* 整块 Erase */
  /* 选择FLASH: CS低电平 */
  SPI_FLASH_CS_LOW();
  /* 发送整块擦除指令*/
  SPI_FLASH_SendByte(W25X_ChipErase);
  /* 停止信号 FLASH: CS 高电平 */
  SPI_FLASH_CS_HIGH();
​
  /* 等待擦除完毕*/
  SPI_FLASH_WaitForWriteEnd();
}
​
/**
  * @brief  对FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区
  * @param  pBuffer,要写入数据的指针
  * @param WriteAddr,写入地址
  * @param  NumByteToWrite,写入数据长度,必须小于等于SPI_FLASH_PerWritePageSize
  * @retval 无
  */
void SPI_FLASH_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
  /* 发送FLASH写使能命令 */
  SPI_FLASH_WriteEnable();
​
  /* 选择FLASH: CS低电平 */
  SPI_FLASH_CS_LOW();
  /* 写页写指令*/
  SPI_FLASH_SendByte(W25X_PageProgram);
  /*发送写地址的高位*/
  SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16);
  /*发送写地址的中位*/
  SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8);
  /*发送写地址的低位*/
  SPI_FLASH_SendByte(WriteAddr & 0xFF);
​
  if(NumByteToWrite > SPI_FLASH_PerWritePageSize)
  {
     NumByteToWrite = SPI_FLASH_PerWritePageSize;
     FLASH_ERROR("SPI_FLASH_PageWrite too large!");
  }
​
  /* 写入数据*/
  while (NumByteToWrite--)
  {
    /* 发送当前要写入的字节数据 */
    SPI_FLASH_SendByte(*pBuffer);
    /* 指向下一字节数据 */
    pBuffer++;
  }
​
  /* 停止信号 FLASH: CS 高电平 */
  SPI_FLASH_CS_HIGH();
​
  /* 等待写入完毕*/
  SPI_FLASH_WaitForWriteEnd();
}
​
/**
  * @brief  对FLASH写入数据,调用本函数写入数据前需要先擦除扇区
  * @param  pBuffer,要写入数据的指针
  * @param  WriteAddr,写入地址
  * @param  NumByteToWrite,写入数据长度
  * @retval 无
  */
void SPI_FLASH_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
  uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
    
    /*mod运算求余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0*/
  Addr = WriteAddr % SPI_FLASH_PageSize;
    
    /*差count个数据值,刚好可以对齐到页地址*/
  count = SPI_FLASH_PageSize - Addr;    
    /*计算出要写多少整数页*/
  NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
    /*mod运算求余,计算出剩余不满一页的字节数*/
  NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
​
     /* Addr=0,则WriteAddr 刚好按页对齐 aligned  */
  if (Addr == 0) 
  {
        /* NumByteToWrite < SPI_FLASH_PageSize */
    if (NumOfPage == 0) 
    {
      SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
    }
    else /* NumByteToWrite > SPI_FLASH_PageSize */
    {
            /*先把整数页都写了*/
      while (NumOfPage--)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
        WriteAddr +=  SPI_FLASH_PageSize;
        pBuffer += SPI_FLASH_PageSize;
      }
            
            /*若有多余的不满一页的数据,把它写完*/
      SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
    }
  }
    /* 若地址与 SPI_FLASH_PageSize 不对齐  */
  else 
  {
        /* NumByteToWrite < SPI_FLASH_PageSize */
    if (NumOfPage == 0) 
    {
            /*当前页剩余的count个位置比NumOfSingle小,写不完*/
      if (NumOfSingle > count) 
      {
        temp = NumOfSingle - count;
                
                /*先写满当前页*/
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
        WriteAddr +=  count;
        pBuffer += count;
                
                /*再写剩余的数据*/
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);
      }
      else /*当前页剩余的count个位置能写完NumOfSingle个数据*/
      {             
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
      }
    }
    else /* NumByteToWrite > SPI_FLASH_PageSize */
    {
            /*地址不对齐多出的count分开处理,不加入这个运算*/
      NumByteToWrite -= count;
      NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
      NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
​
      SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
      WriteAddr +=  count;
      pBuffer += count;
            
            /*把整数页都写了*/
      while (NumOfPage--)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
        WriteAddr +=  SPI_FLASH_PageSize;
        pBuffer += SPI_FLASH_PageSize;
      }
            /*若有多余的不满一页的数据,把它写完*/
      if (NumOfSingle != 0)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
      }
    }
  }
}
​
/**
  * @brief  读取FLASH数据
  * @param  pBuffer,存储读出数据的指针
  * @param   ReadAddr,读取地址
  * @param   NumByteToRead,读取数据长度
  * @retval 无
  */
void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
  /* 选择FLASH: CS低电平 */
  SPI_FLASH_CS_LOW();
​
  /* 发送 读 指令 */
  SPI_FLASH_SendByte(W25X_ReadData);
​
  /* 发送 读 地址高位 */
  SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);
  /* 发送 读 地址中位 */
  SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);
  /* 发送 读 地址低位 */
  SPI_FLASH_SendByte(ReadAddr & 0xFF);
  
    /* 读取数据 */
  while (NumByteToRead--)
  {
    /* 读取一个字节*/
    *pBuffer = SPI_FLASH_SendByte(Dummy_Byte);
    /* 指向下一个字节缓冲区 */
    pBuffer++;
  }
​
  /* 停止信号 FLASH: CS 高电平 */
  SPI_FLASH_CS_HIGH();
}
​
​
​
/*******************************************************************************
* Function Name  : SPI_FLASH_StartReadSequence
* Description    : Initiates a read data byte (READ) sequence from the Flash.
*                  This is done by driving the /CS line low to select the device,
*                  then the READ instruction is transmitted followed by 3 bytes
*                  address. This function exit and keep the /CS line low, so the
*                  Flash still being selected. With this technique the whole
*                  content of the Flash is read with a single READ instruction.
* Input          : - ReadAddr : FLASH's internal address to read from.
* Output         : None
* Return         : None
*******************************************************************************/
void SPI_FLASH_StartReadSequence(uint32_t ReadAddr)
{
  /* Select the FLASH: Chip Select low */
  SPI_FLASH_CS_LOW();
​
  /* Send "Read from Memory " instruction */
  SPI_FLASH_SendByte(W25X_ReadData);
​
  /* Send the 24-bit address of the address to read from -----------------------*/
  /* Send ReadAddr high nibble address byte */
  SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);
  /* Send ReadAddr medium nibble address byte */
  SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);
  /* Send ReadAddr low nibble address byte */
  SPI_FLASH_SendByte(ReadAddr & 0xFF);
}
/*******************************************************************************
* Function Name  : SPI_FLASH_SendHalfWord
* Description    : Sends a Half Word through the SPI interface and return the
*                  Half Word received from the SPI bus.
* Input          : Half Word : Half Word to send.
* Output         : None
* Return         : The value of the received Half Word.
*******************************************************************************/
uint16_t SPI_FLASH_SendHalfWord(uint16_t HalfWord)
{
  
  SPITimeout = SPIT_FLAG_TIMEOUT;
​
  /* Loop while DR register in not emplty */
  while (__HAL_SPI_GET_FLAG( &HandleSPI,  SPI_FLAG_TXE ) == RESET)
  {
    if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(2);
   }
​
  /* Send Half Word through the SPIx peripheral */
  WRITE_REG(HandleSPI.Instance->DR, HalfWord);
​
  SPITimeout = SPIT_FLAG_TIMEOUT;
​
  /* Wait to receive a Half Word */
  while (__HAL_SPI_GET_FLAG( &HandleSPI, SPI_FLAG_RXNE ) == RESET)
   {
    if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(3);
   }
  /* Return the Half Word read from the SPI bus */
  return READ_REG(HandleSPI.Instance->DR);
}
​
​
//进入掉电模式
void SPI_Flash_PowerDown(void)   
{ 
  /* 选择 FLASH: CS 低 */
  SPI_FLASH_CS_LOW();
​
  /* 发送 掉电 命令 */
  SPI_FLASH_SendByte(W25X_PowerDown);
​
  /* 停止信号  FLASH: CS 高 */
  SPI_FLASH_CS_HIGH();
}   
​
//唤醒
void SPI_Flash_WAKEUP(void)   
{
  /*选择 FLASH: CS 低 */
  SPI_FLASH_CS_LOW();
​
  /* 发上 上电 命令 */
  SPI_FLASH_SendByte(W25X_ReleasePowerDown);
​
  /* 停止信号 FLASH: CS 高 */
  SPI_FLASH_CS_HIGH();                   //等待TRES1
} 
​
/**
  * @brief  等待超时回调函数
  * @param  None.
  * @retval None.
  */
static uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{
  /* 等待超时后的处理,输出错误信息 */
  FLASH_ERROR("SPI 等待超时!errorCode = %d",errorCode);
  return 0;
}
​

 

##

STM32F1 HAL DMA UART是指在STM32F1系列微控制器上使用HAL库函数和DMA来实现UART串口通信。引用\[1\]中的代码片段展示了如何配置DMAUART句柄以实现DMA传输数据到UART。首先,需要使能DMA时钟并配置DMA句柄。然后,通过HAL_DMA_Init函数初始化DMA句柄,并通过__HAL_LINKDMA函数将DMA句柄与UART句柄连接起来。这样,在执行UART传输函数之后,DMA将会自动将数据从内存传输到UART外设。引用\[2\]提供了关于UART串口通信过采样数据和利用DMA实现不定长数据接收的更多详细信息。引用\[3\]中的描述说明了在DMA传输完成后,UART状态会从"tx busy"变为"ready"。 #### 引用[.reference_title] - *1* [【STM32HAL库 SPI DMA UART驱动开发](https://blog.csdn.net/zDavid_2018/article/details/107988636)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [【stm32HAL库uart dma收发驱动(含实例)](https://blog.csdn.net/qq_24629659/article/details/129473515)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值