基于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。

相关推荐
HQB20265 小时前
EG1198|屹晶微电子 120V 内置 MOS 异步降压 DC‑DC(高性价比)
嵌入式硬件·硬件工程
果壳science6 小时前
张祥前实验如何获取正电荷
笔记·科技·嵌入式硬件·硬件工程·制造
南山电子nscn6 小时前
中科芯CETC汽车电池管理系统BMS进口芯片国产型号替代指南
单片机·汽车·bms
桀人7 小时前
认识AT24C02
stm32·单片机·嵌入式硬件
大叔学Linux8 小时前
FreeRTOS任务切换笔记
arm开发·笔记
国科安芯8 小时前
星间激光通信终端CANFD控制网络的时延确定性与辐射可靠性
网络·嵌入式硬件·信息与通信·canfd·低轨卫星·商业航天·抗辐射
一直C9 小时前
嵌入式 Linux 驱动开发学习笔记:内核定时器、platform 总线与 I²C 驱动
linux·驱动开发·嵌入式硬件·学习·imx6ull
传感器与混合集成电路9 小时前
150MHz主频、8路DAC与6路PWM:JLH221360伺服控制SIP解析
嵌入式硬件
comyccccc9 小时前
STM32PID控制板设计全解析学习笔记
单片机·嵌入式硬件
Chery11409 小时前
DPE5500 SPI以太网协议栈芯片详解:10/100M以太网、硬件TCP/IP与国产替代方案
arm开发·网络协议·tcp/ip