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。