基于HAL库实现F407的基本外设GPIO输入输出USART收发RTC时钟I2CEEPROM和SPIW25Q128读写及CAN通信

1.LED灯和蜂鸣器按键

这三个外设就是对F407的GPIO口的基本使用,通过GPIO口电平的输出输入来控制和获取外设信息。我们可以直接将F103的程序移植过来,只需要在CubeMX中更改相应的引脚即可。这也就是HAL库比标准库可移植性更好的一个体现,因为其底层帮助我们进行啦对应的宏定义和初始化配置等。CubeMX的配置如下:

程序如下所示:

bash 复制代码
/**
 * @brief 控制LED1 LED2 亮灭的函数
 *
 * @note 详细描述细节,ON 用来控制led亮, OFF用来控制LED熄灭
 *
 * @param device: 可以是LED1, LED2 , 是两个宏,定义在gpio.h中
 * @param cmd: 用来控制LED的亮灭命令
 * ON : 点亮LED
 * OFF :熄灭LED
 * @retval None
 */
void LED_Control(uint8_t device, uint8_t cmd)
{
    if (device == LED1)
    {
        if (cmd == ON) // LED1 ON
        {
            HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
        }
        else if (cmd == OFF) // LED1 OFF
        {
            HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
        }
    }
    else if (device == LED2)
    {
        if (cmd == ON) // LED2 ON
        {
            HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);
        }
        else if (cmd == OFF) // LED2 OFF
        {
            HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);
        }
    }
}
/**
 * @brief LED1 LED2 测试函数
 *
 * @note 验证LED1 LED2的功能
 *
 * @retval None
 */
void LED_Test(void)
{
    LED_Control(LED1, ON);
    LED_Control(LED2, OFF);
    HAL_Delay(500);
    LED_Control(LED1, OFF);
    LED_Control(LED2, ON);
    HAL_Delay(500);
}

/**
 * @brief 控制蜂鸣器的函数
 *
 * @note 详细描述细节,ON 用来控制beep响, OFF用来控制beep不响
 *
 * @param device: 可以是BEEP , 是两个宏,定义在gpio.h中
 * @param cmd: 用来控制BEEP的响与不响命令
 * ON : BEEP响
 * OFF :不响
 * @retval None
 */
void BEEP_Control(uint8_t cmd)
{
    if (cmd == ON)
    {
        HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_SET);
    }
    else if (cmd == OFF)
    {
        HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_RESET);
    }
}
void BEEP_Test(void)
{
    BEEP_Control(ON);
    HAL_Delay(200);
    BEEP_Control(OFF);
    HAL_Delay(800);
}

/**
 * @brief 获取按键的值
 *
 * @note 详细描述细节,根据按下的按键返回按键的键值
 *
 * @param 无
 * @retval 按下的键值 , 定义在gpio.h 当中
 */
uint8_t KEY_Scan(void)
{
    // 实现扫描获取键值的代码
    uint8_t keyval = 0;
    // OK 按键被按下后,引脚变为低电平
    if (HAL_GPIO_ReadPin(KEY_OK_GPIO_Port, KEY_OK_Pin) == GPIO_PIN_RESET)
    {
        keyval = KEY_OK;
    }
    // ESC 按键被按下后,引脚变为低电平
    else if (HAL_GPIO_ReadPin(KEY_ESC_GPIO_Port, KEY_ESC_Pin) == GPIO_PIN_RESET)
    {
        keyval = KEY_ESC;
    }
    return keyval; // 没有按键按下
}
void KEY_Test(void)
{
    uint8_t keyval = KEY_Scan();
    if (keyval > 0)
    {
        if (keyval == KEY_OK)
        {
            LED_Control(LED1, ON);
        }
        else if (keyval == KEY_ESC)
        {
            LED_Control(LED1, OFF);
        }
    }
}

并且我们用了一个按键来进行外部中断的使用程序如下:

bash 复制代码
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(GPIO_Pin == KEY_ESC_Pin)
	{
		HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
	}
}

2.单个串口的收发以及串口之间的收发

对于串口的收发我们在F103时详细说过,接收有查询,中断,DMA的方式,我们这里就用最常用的DMA加空闲中断接收不定长数据的方式来实现数据的接收。CubeMX配置如下:

程序如下所示:

bash 复制代码
// DMA 接收到一半的中断
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)
    {
        uint8_t Length = DMA_BUF_SIZE / 2 - RX1_Offset;
        // printf("HLength=%d\n",Length);
        HAL_UART_Transmit(huart, RX1_Buf + RX1_Offset, Length, HAL_MAX_DELAY);
        RX1_Offset += Length;
    }
}
// DMA传输完成中断 , 就是接收满了的时候 触发中断
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)
    {
        uint8_t Length = DMA_BUF_SIZE - RX1_Offset;
        HAL_UART_Transmit(huart, RX1_Buf + RX1_Offset, Length, HAL_MAX_DELAY);
        // printf("CLength=%d\n",Length);
        RX1_Offset = 0; // 清空dma 位置基准值
    }
}
// 用户自定义的函数 , 处理串口空闲中断
void USER_UART_IRQHandler(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1) // 判断是否是串口1
    {
        if (__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE) != RESET) // 判断是否是空闲中断
        {
            __HAL_UART_CLEAR_IDLEFLAG(huart); // 清除空闲中断标志(否则会一直不断进入中断)
            // 计算接收到的数据长度 : BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx)
            uint8_t Length = DMA_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx) - RX1_Offset;
            HAL_UART_Transmit(huart, RX1_Buf + RX1_Offset, Length, HAL_MAX_DELAY);
            RX1_Offset += Length;
            // printf("ILength=%d\n",Length);
        }
    }
}

可见得益于HAL库好的可移植性,主要的函数基本上和F103的程序一样。在应用中我们也会常用到串口之间数据的收发,下面我们来看两个串口之间的收发,同样对于接收数据我们采用DMA加空闲中断来接收不定长数据。在CubeMX的配置中我们只需要根据串口1的配置再启用一个串口3即可配置一样。程序如下:

bash 复制代码
// DMA 接收到一半的中断
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)
    {
        uint8_t Length = DMA_BUF_SIZE / 2 - RX1_Offset;
        // printf("HLength=%d\n",Length);
        HAL_UART_Transmit(&huart3, RX1_Buf + RX1_Offset, Length, HAL_MAX_DELAY);
        RX1_Offset += Length;
    }
		else if (huart->Instance == USART3)
    {
        uint8_t Length = DMA_BUF_SIZE / 2 - RX3_Offset;
        // printf("HLength=%d\n",Length);
        HAL_UART_Transmit(&huart1, RX3_Buf + RX3_Offset, Length, HAL_MAX_DELAY);
        RX3_Offset += Length;
    }
}
// DMA传输完成中断 , 就是接收满了的时候 触发中断
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)
    {
        uint8_t Length = DMA_BUF_SIZE - RX1_Offset;
        HAL_UART_Transmit(&huart3, RX1_Buf + RX1_Offset, Length, HAL_MAX_DELAY);
        // printf("CLength=%d\n",Length);
        RX1_Offset = 0; // 清空dma 位置基准值
    }
		else if (huart->Instance == USART3)
    {
        uint8_t Length = DMA_BUF_SIZE - RX3_Offset;
        HAL_UART_Transmit(&huart1, RX3_Buf + RX3_Offset, Length, HAL_MAX_DELAY);
        // printf("CLength=%d\n",Length);
        RX3_Offset = 0; // 清空dma 位置基准值
    }
}
// 用户自定义的函数 , 处理串口空闲中断
void USER_UART_IRQHandler(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1) // 判断是否是串口1
    {
        if (__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE) != RESET) // 判断是否是空闲中断
        {
            __HAL_UART_CLEAR_IDLEFLAG(huart); // 清除空闲中断标志(否则会一直不断进入中断)
            // 计算接收到的数据长度 : BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx)
            uint8_t Length = DMA_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx) - RX1_Offset;
            HAL_UART_Transmit(&huart3, RX1_Buf + RX1_Offset, Length, HAL_MAX_DELAY);
            RX1_Offset += Length;
            // printf("ILength=%d\n",Length);
        }
    }
		else if (huart->Instance == USART3) // 判断是否是串口1
    {
        if (__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE) != RESET) // 判断是否是空闲中断
        {
            __HAL_UART_CLEAR_IDLEFLAG(huart); // 清除空闲中断标志(否则会一直不断进入中断)
            // 计算接收到的数据长度 : BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx)
            uint8_t Length = DMA_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart3_rx) - RX3_Offset;
            HAL_UART_Transmit(&huart1, RX3_Buf + RX3_Offset, Length, HAL_MAX_DELAY);
            RX3_Offset += Length;
            // printf("ILength=%d\n",Length);
        }
    }
}

3.实时时钟RTC的实现

对于实时时钟的实现CubeMX的配置如下和F103有着细微的差异:

日历的配置可根据自己的需求进行配置。然后我们要启用bkp备份寄存器来实现实时时钟的掉电不丢失,时钟可以一直走。程序如下:

bash 复制代码
	if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1) != 0x5051)//判断bkp寄存器中保存的标志位来看是否要对rtc日历进行初始化
	{
  /* USER CODE END Check_RTC_BKUP */

  /** Initialize RTC and set the Time and Date
  */
  sTime.Hours = 0x22;
  sTime.Minutes = 0x0;
  sTime.Seconds = 0x0;
  sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  sTime.StoreOperation = RTC_STOREOPERATION_RESET;
  if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
  {
    Error_Handler();
  }
  sDate.WeekDay = RTC_WEEKDAY_TUESDAY;
  sDate.Month = RTC_MONTH_NOVEMBER;
  sDate.Date = 0x18;
  sDate.Year = 0x25;

  if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
  {
    Error_Handler();
  }

  /** Enable Calibrartion
  */
  if (HAL_RTCEx_SetCalibrationOutPut(&hrtc, RTC_CALIBOUTPUT_1HZ) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN RTC_Init 2 */
	HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0x5051); // 设置标志位, 表示时间已经被设置
	}
}

void RTC_Test(void)
{
    RTC_TimeTypeDef Time = {0};
    RTC_DateTypeDef Date = {0};
    HAL_RTC_GetTime(&hrtc, &Time, RTC_FORMAT_BIN); // 获取时分秒
    HAL_RTC_GetDate(&hrtc, &Date, RTC_FORMAT_BIN); // 获取年月日
    printf("%04d-%02d-%02d %02d:%02d:%02d\n", 2000 + Date.Year, Date.Month, Date.Date,
           Time.Hours, Time.Minutes, Time.Seconds);
    HAL_Delay(1000);
}

4.I2C实现EEPROM的读写和SPI实现W25Q128的读写

eeprom和w25q128都是掉电不丢失的存储芯片,可以用来对需要存储的数据进行存储。首先我们来看I2C实现EEPROM读写的CubeMX配置和程序:

在F407启用相应的I2C,SPI,串口时一定要和硬件连接相对于,因为其引脚多有很多复用关系,要明确和硬件连接相对应。

bash 复制代码
void EEPROM_Test(void)
{
#define N 256
#define AT24C02_ADDRESS_WRITE 0xA0
#define AT24C02_ADDRESS_READ 0xA1
    uint8_t src[N] = {0};
    uint8_t dst[N] = {0};
    for (uint16_t i = 0; i < N; i++)
    {
        src[i] = i;
        dst[i] = 0;
    }
    printf("writing data to eeprom...\n");
#if 0
// 每次写1个字节
for(uint16_t i=0;i < N;i++)
{
// i 是eeprom的内存地址
// AT24C02_ADDRESS_WRITE 是24c02的地址
// 1000 是超时时间
HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDRESS_WRITE,i,I2C_MEMADD_SIZE_8BIT,&src[i],1, 1000);
HAL_Delay(5) ; // 这个延时必须的 , 等待数据写入到掉电非易失区
}
#else
    // 每次写8个字节 , 每次写1页
    for (uint16_t i = 0; i < N; i += 8)
    {
        // i 是eeprom的内存地址
        // AT24C02_ADDRESS_WRITE 是24c02的地址
        // 1000 是超时时间
        HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDRESS_WRITE, i, I2C_MEMADD_SIZE_8BIT, &src[i], 8, 1000);
        HAL_Delay(5); // 这个延时必须的 , 等待数据写入到掉电非易失区
    }
#endif
    printf("read from eeprom...\n");
    HAL_Delay(20); // 等待eeprom 操作完成
#if 0
// 1次读1个字节
for(uint16_t i =0 ;i< N;i++)
{
// i 是eeprom的内存地址
// AT24C02_ADDRESS_READ 是24c02的地址
// 1000 是超时时间
HAL_I2C_Mem_Read(&hi2c1, AT24C02_ADDRESS_READ,i,I2C_MEMADD_SIZE_8BIT,&dst[i],1,1000);
HAL_Delay(1);
}
#else
    // 0 : 表示的是起始地址
    // dst : 这个的地址会自动增加
    // N : 表示地址增加的数量
    HAL_I2C_Mem_Read(&hi2c1, AT24C02_ADDRESS_READ, 0, I2C_MEMADD_SIZE_8BIT, dst, N, 1000);
#endif
    for (int32_t i = 0; i < N; i++)
    {
        printf("0x%02X ", dst[i]);
    }
    printf("\n");
    if (memcmp(dst, src, N) == 0)
    {
        printf("eeprom test ok\n");
    }
    else
    {
        printf("eeprom test fail\n");
    }
}

SPI实现W25Q128的读写,CubeMX配置和程序如下:

bash 复制代码
#ifndef __W25Qxx_H
#define __W25Qxx_H
 
/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx.h"
#include "spi.h"
	 
#define W25Q128FV_FLASH_SIZE                  0x1000000 /* 128 MBits => 16MBytes */
#define W25Q128FV_SECTOR_SIZE                 0x10000   /* 256 sectors of 64KBytes */
#define W25Q128FV_SUBSECTOR_SIZE              0x1000    /* 4096 subsectors of 4kBytes */
#define W25Q128FV_PAGE_SIZE                   0x100     /* 65536 pages of 256 bytes */
 
#define W25Q128FV_DUMMY_CYCLES_READ           4
#define W25Q128FV_DUMMY_CYCLES_READ_QUAD      10
 
#define W25Q128FV_BULK_ERASE_MAX_TIME         250000
#define W25Q128FV_SECTOR_ERASE_MAX_TIME       3000
#define W25Q128FV_SUBSECTOR_ERASE_MAX_TIME    800
#define W25Qx_TIMEOUT_VALUE 1000
 
/* Reset Operations */
#define RESET_ENABLE_CMD                     0x66
#define RESET_MEMORY_CMD                     0x99
 
#define ENTER_QPI_MODE_CMD                   0x38
#define EXIT_QPI_MODE_CMD                    0xFF
 
/* Identification Operations */
#define READ_ID_CMD                          0x90
#define DUAL_READ_ID_CMD                     0x92
#define QUAD_READ_ID_CMD                     0x94
#define READ_JEDEC_ID_CMD                    0x9F
 
/* Read Operations */
#define READ_CMD                             0x03
#define FAST_READ_CMD                        0x0B
#define DUAL_OUT_FAST_READ_CMD               0x3B
#define DUAL_INOUT_FAST_READ_CMD             0xBB
#define QUAD_OUT_FAST_READ_CMD               0x6B
#define QUAD_INOUT_FAST_READ_CMD             0xEB
 
/* Write Operations */
#define WRITE_ENABLE_CMD                     0x06
#define WRITE_DISABLE_CMD                    0x04
 
/* Register Operations */
#define READ_STATUS_REG1_CMD                  0x05
#define READ_STATUS_REG2_CMD                  0x35
#define READ_STATUS_REG3_CMD                  0x15
 
#define WRITE_STATUS_REG1_CMD                 0x01
#define WRITE_STATUS_REG2_CMD                 0x31
#define WRITE_STATUS_REG3_CMD                 0x11
 
 
/* Program Operations */
#define PAGE_PROG_CMD                        0x02
#define QUAD_INPUT_PAGE_PROG_CMD             0x32
 
 
/* Erase Operations */
#define SECTOR_ERASE_CMD                     0x20
#define CHIP_ERASE_CMD                       0xC7
 
#define PROG_ERASE_RESUME_CMD                0x7A
#define PROG_ERASE_SUSPEND_CMD               0x75
 
 
/* Flag Status Register */
#define W25Q128FV_FSR_BUSY                    ((uint8_t)0x01)    /*!< busy */
#define W25Q128FV_FSR_WREN                    ((uint8_t)0x02)    /*!< write enable */
#define W25Q128FV_FSR_QE                      ((uint8_t)0x02)    /*!< quad enable */
 
 
#define W25Qx_Enable() 			HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET)
#define W25Qx_Disable() 		HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET)
 
#define W25Qx_OK            ((uint8_t)0x00)
#define W25Qx_ERROR         ((uint8_t)0x01)
#define W25Qx_BUSY          ((uint8_t)0x02)
#define W25Qx_TIMEOUT		((uint8_t)0x03)
 
 
uint8_t W25Qx_Init(void);
static void	W25Qx_Reset(void);
static uint8_t W25Qx_GetStatus(void);
uint8_t W25Qx_WriteEnable(void);
void W25Qx_Read_ID(uint8_t *ID);
uint8_t W25Qx_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size);
uint8_t W25Qx_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size);
uint8_t W25Qx_Erase_Block(uint32_t Address);
uint8_t W25Qx_Erase_Chip(void);
void W25Qx_Test(void);

 
#endif 




#include <string.h>
#include <stdio.h>
#include "w25qx.h"

 /**********************************************************************************
  * 函数功能: 模块初始化
  */
uint8_t W25Qx_Init(void)
{ 	
	W25Qx_Reset();	
	return W25Qx_GetStatus();
}
 
 
static void	W25Qx_Reset(void)
{
	uint8_t cmd[2] = {RESET_ENABLE_CMD,RESET_MEMORY_CMD};
	
	W25Qx_Enable();

	/* Send the reset command */
	HAL_SPI_Transmit(&hspi1, cmd, 2, W25Qx_TIMEOUT_VALUE);	
	
	W25Qx_Disable();

 
}
 
 /**********************************************************************************
  * 函数功能: 获取设备状态
  */
static uint8_t W25Qx_GetStatus(void)
{
	uint8_t cmd[] = {READ_STATUS_REG1_CMD};
	uint8_t status;
	
	W25Qx_Enable();
	/* Send the read status command */
	HAL_SPI_Transmit(&hspi1, cmd, 1, W25Qx_TIMEOUT_VALUE);	
	/* Reception of the data */
	HAL_SPI_Receive(&hspi1,&status, 1, W25Qx_TIMEOUT_VALUE);
	W25Qx_Disable();
	
	/* Check the value of the register */
  if((status & W25Q128FV_FSR_BUSY) != 0)
  {
    return W25Qx_BUSY;
  }
	else
	{
		return W25Qx_OK;
	}		
}
 
 /**********************************************************************************
  * 函数功能: 写使能
  */
uint8_t W25Qx_WriteEnable(void)
{
	uint8_t cmd[] = {WRITE_ENABLE_CMD};
	uint32_t tickstart = HAL_GetTick();
 
	/*Select the FLASH: Chip Select low */
	W25Qx_Enable();
	/* Send the read ID command */
	HAL_SPI_Transmit(&hspi1, cmd, 1, W25Qx_TIMEOUT_VALUE);	
	/*Deselect the FLASH: Chip Select high */
	W25Qx_Disable();
	
	/* Wait the end of Flash writing */
	while(W25Qx_GetStatus() == W25Qx_BUSY)
	{
		/* Check for the Timeout */
    if((HAL_GetTick() - tickstart) > W25Qx_TIMEOUT_VALUE)
    {        
			return W25Qx_TIMEOUT;
    }
	}
	return W25Qx_OK;
}
 
 /**********************************************************************************
  * 函数功能: 获取设备ID
  */
void W25Qx_Read_ID(uint8_t *ID)
{
	uint8_t cmd[4] = {READ_ID_CMD,0x00,0x00,0x00};
	
	W25Qx_Enable();
	/* Send the read ID command */
	HAL_SPI_Transmit(&hspi1, cmd, 4, W25Qx_TIMEOUT_VALUE);	
	/* Reception of the data */
	HAL_SPI_Receive(&hspi1,ID, 2, W25Qx_TIMEOUT_VALUE);
	W25Qx_Disable();
		
}
 
 /**********************************************************************************
  * 函数功能: 读数据
  * 输入参数: 缓存数组指针、读地址、字节数
  */
uint8_t W25Qx_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size)
{
	uint8_t cmd[4];
 
	/* Configure the command */
	cmd[0] = READ_CMD;
	cmd[1] = (uint8_t)(ReadAddr >> 16);
	cmd[2] = (uint8_t)(ReadAddr >> 8);
	cmd[3] = (uint8_t)(ReadAddr);
	
	W25Qx_Enable();
	/* Send the read ID command */
	HAL_SPI_Transmit(&hspi1, cmd, 4, W25Qx_TIMEOUT_VALUE);	
	/* Reception of the data */
	if (HAL_SPI_Receive(&hspi1, pData,Size,W25Qx_TIMEOUT_VALUE) != HAL_OK)
  {
    return W25Qx_ERROR;
  }
	W25Qx_Disable();
	return W25Qx_OK;
}
 
 /**********************************************************************************
  * 函数功能: 写数据
  * 输入参数: 缓存数组指针、写地址、字节数
  */
uint8_t W25Qx_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size)
{
	uint8_t cmd[4];
	uint32_t end_addr, current_size, current_addr;
	uint32_t tickstart = HAL_GetTick();
	
	/* Calculation of the size between the write address and the end of the page */
  current_addr = 0;
 
  while (current_addr <= WriteAddr)
  {
    current_addr += W25Q128FV_PAGE_SIZE;
  }
  current_size = current_addr - WriteAddr;
 
  /* Check if the size of the data is less than the remaining place in the page */
  if (current_size > Size)
  {
    current_size = Size;
  }
 
  /* Initialize the adress variables */
  current_addr = WriteAddr;
  end_addr = WriteAddr + Size;
	
  /* Perform the write page by page */
  do
  {
		/* Configure the command */
		cmd[0] = PAGE_PROG_CMD;
		cmd[1] = (uint8_t)(current_addr >> 16);
		cmd[2] = (uint8_t)(current_addr >> 8);
		cmd[3] = (uint8_t)(current_addr);
 
		/* Enable write operations */
		W25Qx_WriteEnable();
	
		W25Qx_Enable();
    /* Send the command */
    if (HAL_SPI_Transmit(&hspi1,cmd, 4, W25Qx_TIMEOUT_VALUE) != HAL_OK)
    {
      return W25Qx_ERROR;
    }
    
    /* Transmission of the data */
    if (HAL_SPI_Transmit(&hspi1, pData,current_size, W25Qx_TIMEOUT_VALUE) != HAL_OK)
    {
      return W25Qx_ERROR;
    }
			W25Qx_Disable();
    	/* Wait the end of Flash writing */
		while(W25Qx_GetStatus() == W25Qx_BUSY)
		{
			/* Check for the Timeout */
			if((HAL_GetTick() - tickstart) > W25Qx_TIMEOUT_VALUE)
			{        
				return W25Qx_TIMEOUT;
			}
		}
    
    /* Update the address and size variables for next page programming */
    current_addr += current_size;
    pData += current_size;
    current_size = ((current_addr + W25Q128FV_PAGE_SIZE) > end_addr) ? (end_addr - current_addr) : 
      W25Q128FV_PAGE_SIZE;
  } while (current_addr < end_addr);
 
	
	return W25Qx_OK;
}
 
 /**********************************************************************************
  * 函数功能: 扇区擦除
  * 输入参数: 地址
  */
uint8_t W25Qx_Erase_Block(uint32_t Address)
{
	uint8_t cmd[4];
	uint32_t tickstart = HAL_GetTick();
	cmd[0] = SECTOR_ERASE_CMD;
	cmd[1] = (uint8_t)(Address >> 16);
	cmd[2] = (uint8_t)(Address >> 8);
	cmd[3] = (uint8_t)(Address);
	
	/* Enable write operations */
	W25Qx_WriteEnable();
	
	/*Select the FLASH: Chip Select low */
	W25Qx_Enable();
	/* Send the read ID command */
	HAL_SPI_Transmit(&hspi1, cmd, 4, W25Qx_TIMEOUT_VALUE);	
	/*Deselect the FLASH: Chip Select high */
	W25Qx_Disable();
	
	/* Wait the end of Flash writing */
	while(W25Qx_GetStatus() == W25Qx_BUSY)
	{
		/* Check for the Timeout */
    if((HAL_GetTick() - tickstart) > W25Q128FV_SECTOR_ERASE_MAX_TIME)
    {        
			return W25Qx_TIMEOUT;
    }
	}
	return W25Qx_OK;
}
 
 /**********************************************************************************
  * 函数功能: 芯片擦除
  */
uint8_t W25Qx_Erase_Chip(void)
{
	uint8_t cmd[4];
	uint32_t tickstart = HAL_GetTick();
	cmd[0] = CHIP_ERASE_CMD;
	
	/* Enable write operations */
	W25Qx_WriteEnable();
	
	/*Select the FLASH: Chip Select low */
	W25Qx_Enable();
	/* Send the read ID command */
	HAL_SPI_Transmit(&hspi1, cmd, 1, W25Qx_TIMEOUT_VALUE);	
	/*Deselect the FLASH: Chip Select high */
	W25Qx_Disable();
	
	/* Wait the end of Flash writing */
	while(W25Qx_GetStatus() != W25Qx_BUSY)
	{
		/* Check for the Timeout */
    if((HAL_GetTick() - tickstart) > W25Q128FV_BULK_ERASE_MAX_TIME)
    {        
			return W25Qx_TIMEOUT;
    }
	}
	return W25Qx_OK;
}
void W25Qx_Test(void)
{
	
	uint8_t wData[0x100];   //写缓存数组
	uint8_t rData[0x100];   //读缓存数组
	uint8_t ID[4];          //设备ID缓存数组
	/*-Step1- 验证设备ID  ************************************************Step1*/ 
	
	W25Qx_Read_ID(ID);
	//第一位厂商ID固定0xEF,第二位设备ID根据容量不同,具体为:
	//W25Q16为0x14、32为0x15、40为0x12、64为0x16、80为0x13、128为0x17
	if((ID[0] != 0xEF) | (ID[1] != 0x17)) 
	{                                
		printf("something wrong in Step1 \n");
	}
	else
	{
		printf("W25Qxx ID is : ");
		for(uint32_t i=0;i<2;i++)
		{
			printf("0x%02X ",ID[i]);
		}
		printf("\n");
	}
#if 1
	/*-Step2- 擦除块  ************************************************Step2*/ 	
	if(W25Qx_Erase_Block(0) == W25Qx_OK)
		printf("QSPI Erase Block OK!\n");
	else
		printf("something wrong in Step2\n");
	/*-Step3- 写数据  ************************************************Step3*/	
	for(uint32_t i =0;i<256;i ++)
	{
			wData[i] = i;
      rData[i] = 0;
	}
	
	if(W25Qx_Write(wData,0,256)== W25Qx_OK)
		printf("QSPI Write OK!\n");
	else
		printf("something wrong in Step3\n");
    /*-Step4- 读数据  ************************************************Step4*/	
	if(W25Qx_Read(rData,0x00,0x100)== W25Qx_OK)
		printf("QSPI Read ok\n");
	else
		printf("something wrong in Step4\n");
	
	printf("QSPI Read Data : \n");
	for(uint32_t i =0;i<256;i++)
	{
		printf("0x%02X ",rData[i]); 
	}	
	printf("\n");
	/*-Step5- 数据对比  ************************************************Step5*/		
	if(memcmp(wData,rData,0x100) == 0 ) 
	{
			printf("W25Q128FV QuadSPI Test OK\n");
	}
	else
	{
			printf("W25Q128FV QuadSPI Test False\n");
	}
		
	
	#endif
}

5.CAN通信

在F407中有两个CAN接口,可用作主CAN和从CAN,所以我们来实现一些双CAN通信,通过CAN1发送CAN2接收CAN2发送CAN2接收。CubeMX配置和程序如下:

CAN2的CubeMX配置和CAN1一样。下面我们看程序:

bash 复制代码
/ CubeMX只帮助我们生成can初始化相关程序,筛选器的配置需要我们来完成
// 设置筛选器,要在完成CAN初始化之后调用此函数
HAL_StatusTypeDef CAN1_SetFilters(void)
{
    CAN_FilterTypeDef canFilter; // 筛选器结构体变量,该结构体在HAL库CAN扩展程序中已经被定义
    // Configure the CAN Filter
    canFilter.FilterBank = 0;                      // 筛选器组编号
    canFilter.FilterMode = CAN_FILTERMODE_IDMASK;  // ID掩码模式
    canFilter.FilterScale = CAN_FILTERSCALE_32BIT; // 32位长度
    // 设置1:接收所有帧
    canFilter.FilterIdHigh = 0x0000;                   // CAN_FxR1 的高16位
    canFilter.FilterIdLow = 0x0000;                    // CAN_FxR1 的低16位
    canFilter.FilterMaskIdHigh = 0x0000;               // CAN_FxR2的高16位。所有位任意
    canFilter.FilterMaskIdLow = 0x0000;                // CAN_FxR2的低16位,所有位任意
    canFilter.FilterFIFOAssignment = CAN_FILTER_FIFO0; // 应用于FIFO0
    canFilter.FilterActivation = ENABLE;               // 使能筛选器
    canFilter.SlaveStartFilterBank = 14;               // 从CAN控制器筛选器起始的Bank
    HAL_StatusTypeDef result = HAL_CAN_ConfigFilter(&hcan1, &canFilter); //HAL库提供CAN筛选器配置函数
    return result;
}

// 设置筛选器,要在完成CAN初始化之后调用此函数
HAL_StatusTypeDef CAN2_SetFilters(void)
{
    CAN_FilterTypeDef canFilter; // 筛选器结构体变量
    // Configure the CAN Filter
    canFilter.FilterBank = 14;                     // 筛选器组编号
    canFilter.FilterMode = CAN_FILTERMODE_IDMASK;  // ID掩码模式
    canFilter.FilterScale = CAN_FILTERSCALE_32BIT; // 32位长度
    // 设置1:接收所有帧
    canFilter.FilterIdHigh = 0x0000;                   // CAN_FxR1 的高16位
    canFilter.FilterIdLow = 0x0000;                    // CAN_FxR1 的低16位
    canFilter.FilterMaskIdHigh = 0x0000;               // CAN_FxR2的高16位。所有位任意
    canFilter.FilterMaskIdLow = 0x0000;                // CAN_FxR2的低16位,所有位任意
    canFilter.FilterFIFOAssignment = CAN_FILTER_FIFO0; // 应用于FIFO0
    canFilter.FilterActivation = ENABLE;               // 使能筛选器
    canFilter.SlaveStartFilterBank = 14;               // 从CAN控制器筛选器起始的Bank
    HAL_StatusTypeDef result = HAL_CAN_ConfigFilter(&hcan2, &canFilter);
    return result;
}

//完成CAN筛选器配置启动CAN模块和接收发送中断
void CAN_Init(void)
{
    /***************************CAN1****************************/
    if (CAN1_SetFilters() == HAL_OK) // 设置筛选器
        printf("CAN1 SetFilters\n");
    if (HAL_CAN_Start(&hcan1) == HAL_OK) // 启动CAN1模块
        printf("CAN1 started\n");
    // 启用CAN发送/接收中断
    if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING | CAN_IT_TX_MAILBOX_EMPTY) !=
        HAL_OK)
    {
        printf("CAN_IT_RX_FIFO0_MSG_PENDING Enable Fail\n");
        Error_Handler();
    }
    /***************************CAN2****************************/
    if (CAN2_SetFilters() == HAL_OK) // 设置筛选器
        printf("CAN2 SetFilters\n");
    if (HAL_CAN_Start(&hcan2) == HAL_OK) // 启动CAN2模块
        printf("CAN2 started\n");
    // 启用CAN发送/接收中断
    if (HAL_CAN_ActivateNotification(&hcan2, CAN_IT_RX_FIFO0_MSG_PENDING | CAN_IT_TX_MAILBOX_EMPTY) !=
        HAL_OK)
    {
        printf("CAN_IT_RX_FIFO0_MSG_PENDING Enable Fail\n");
        Error_Handler();
    }
}

// CAN发送数据测试函数
void CAN1_Send_Test(void)
{
    uint8_t data[8] = {1, 2, 3, 4, 5, 6, 7, 8};
    TxMessage.IDE = CAN_ID_STD;   // 设置ID类型
    TxMessage.StdId = 0x12;       // 设置ID号
    TxMessage.RTR = CAN_RTR_DATA; // 设置传送数据帧
    TxMessage.DLC = 8;            // 设置数据长度
    if (HAL_CAN_AddTxMessage(&hcan1, &TxMessage, data, &TxMailbox) != HAL_OK)
    {
        printf("CAN1 send data fail!\r\n");
        Error_Handler();
    }
}
void CAN2_Send_Test(void)
{
    uint8_t data[8] = {0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18};
    TxMessage.IDE = CAN_ID_STD;   // 设置ID类型
    TxMessage.StdId = 0x13;       // 设置ID号
    TxMessage.RTR = CAN_RTR_DATA; // 设置传送数据帧
    TxMessage.DLC = 8;            // 设置数据长度
    if (HAL_CAN_AddTxMessage(&hcan2, &TxMessage, data, &TxMailbox) != HAL_OK)
    {
        printf("CAN2 send data fail!\r\n");
        Error_Handler();
    }
}
// CAN接收中断处理函数
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
    uint8_t data[8];
    HAL_StatusTypeDef status;
    if (hcan == &hcan1)
    {
        status = HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxMessage, data);
        if (HAL_OK == status)
        {
            printf("--->CAN1 Receieved!\r\n");
            printf(" Id:%#x\n", RxMessage.StdId);
            printf("Data:");
            for (uint8_t i = 0; i < 8; i++)
            {
                printf("0x%02x ", data[i]);
            }
            printf("\n");
        }
    }
    else if (hcan == &hcan2)
    {
        status = HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxMessage, data);
        if (HAL_OK == status)
        {
            printf("--->CAN2 Receieved!\r\n");
            printf(" Id:%#x\n", RxMessage.StdId);
            printf("Data:");
            for (uint8_t i = 0; i < 8; i++)
            {
                printf("0x%02x ", data[i]);
            }
            printf("\n");
        }
    }
}
/*CAN发送完成中断处理函数*/
void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan)
{
    if (hcan == &hcan1)
    {
        printf("--->CAN1 Send Data OK!\n");
    }
    else if (hcan == &hcan2)
    {
        printf("--->CAN2 Send Data OK!\n");
    }
}

在对CAN筛选器配置时要注意,CAN1和CAN2要使用不同的筛选器。CAN1默认的筛选器组是0到13为了筛选器使用不冲突所以我们CAN2的筛选器组要使用14到27,所以要设置从CAN的筛选器起始编号为14。

相关推荐
就是蠢啊2 小时前
51单片机——数码管
单片机·嵌入式硬件·51单片机
别掩3 小时前
三极管恒流电路
单片机·嵌入式硬件
花落已飘3 小时前
STM32 SDIO接口介绍
stm32·单片机·嵌入式硬件
DIY机器人工房4 小时前
嵌入式面试题:了解软件SPI和软件I2C吗?说一说。
stm32·单片机·嵌入式硬件
shuidaoyuxing6 小时前
嵌入式系统系统讲解
单片机·嵌入式硬件
brave and determined7 小时前
可编程逻辑器件学习(day26):低主频FPGA为何能碾压高主频CPU?
人工智能·嵌入式硬件·深度学习·学习·fpga开发·云计算·fpga
三品吉他手会点灯7 小时前
stm32f103学习笔记-17-STM32 中断应用总结
笔记·stm32·单片机·嵌入式硬件·学习
the sun348 小时前
数电基础:移位寄存器、顺序脉冲、序列信号发生器
单片机·嵌入式硬件·fpga开发·数电
Bona Sun8 小时前
单片机手搓掌上游戏机(十)—esp8266运行gameboy模拟器之硬件准备
c++·单片机·游戏机