STM32基于CubeIDE和HAL库 基础入门学习笔记:功能驱动与应用

文章目录:

一:LED与按键驱动程序

main.c

1.闪灯 

led.h

led.c 

2.按键控制LED亮灭 

key.h 

key.c

二:蜂鸣器与继电器驱动程序

main.c

1.蜂鸣器

buzzer.h

buzzer.c

delay.h

delay.c

2.继电器

relay.h

relay.c

三:USART串口收发测试程序(超级终端)

main.c

retarget.h

retarget.c

usart.h

usart.c

四:ADC与DMA驱动程序

1.ADC读取电位器与光敏电阻测试程序(通过DMA驱动函数的方式,用时长) 

main.c

adc.h

adc.c

2.DMA 

2.1 用DMA读取单路ADC测试程序 

main.c

2.2 用DMA读取多路ADC测试程序

main.c

五:RTC与BKP驱动程序

1.HAL库自带的RTC时钟测试程序

main.c

2.创建走时完善的RTC时钟测试程序

main.c

rtc.h

rtc.c

六:温湿度传感器DHT11芯片驱动程序

dht11.h

dht11.c

main.c

七:SPI总线闪存芯片驱动程序

w25qxx.h

w25qxx.c

main.c

八:USB从设备串口驱动程序

usbd_cdc_if.h

usbd_cdc_if.c

main.c

九:省电模式、CRC与芯片ID

1.省电模式(睡眠、停机、待机) 

1.1 睡眠模式测试程序

1.2 停机模式测试程序

1.3 待机模式测试程序

2.CRC数据校验方式与芯片ID测试程序

十:外部中断与定时器

1.外部中断(外部中断的按键测试程序)

stm32f1xx_it.c

main.c

key.c

2.定时器

2.1 定时器中断的闪灯测试程序

2.2 定时器中断的PWM调光测试程序

十一:RS485总线有线通讯驱动程序

rs485.h

rs485.c

usart.c

main.c

十二:CAN总线有线通讯驱动程序

can1.h

can1.c

main.c


文件夹结构

        Core             存放着内核代码
            Inc:用于存敝各功能的h库文件
            Src(main.c):用于存敝各功能的c文件
            Starup:用于存放汇编语言的单片机启动文件
        Debug            存放着与仿真器调试相关的文件,里面有“.hex”文件
        Drivers          存放着HAL库和相关的驱动程序文件
            CMSIS:软件接口标准化文件(内核与硬件之间的底层协议)
                Device:STM32F1单片机的底层库文件
                Include:ARM内核与STM32F1单片机硬件之间的底层协议库文件
            STM32Fx_Driver:HAL库文件
                HAL库各功能的h文件
                HAL库各功能的c文件
        icode            存放"用户自建"的板级硬件驱动程序
        Middlewares      存放着与“中间件”相关的驱动程序文件
        USB_DEVICE       存放着USB从设备的驱动程序文件
        .project         CubeIDE工程的启动文件
        .ioc             CubeMX图形界面的启动文件

 创建驱动程序文件 

1.创建驱动程序总文件夹:鼠标右键点击工程名——>点击新建——>点击Source Floder源文件夹——>文件夹名称A“icode”——>点击完成

2.创建驱动程序文件夹  :点击(A)icode文件夹鼠标右键——>点击新建——>点击文件夹——>文件夹命名"B"——>点击完成
  2.1 创建.c源文件    :点击B文件夹鼠标右键——>点击新建——>点击Source File源文件——>文件夹命名"C.c"——>点击完成

  2.2 创建.h头文件    :点击B文件夹鼠标右键——>点击新建——>点击Header File头文件——>文件夹命名"C.h"——>点击完成

一:LED与按键驱动程序

main.c

#include "main.h"
#include "../../icode/led/led.h"
#include "../../icode/key/key.h"


int main(void)
{

	  if(KEY_1()) //按键KEY1判断为1时按键按下
	  {
		  LED_1(1);//LED1灯控制(1点亮,0熄灭)
		  LED_2(1);//LED2灯控制(1点亮,0熄灭)
	  }
	  if(KEY_2()) //按键KEY2判断为1时按键按下
	  {
		  LED_1(0);//LED1灯控制(1点亮,0熄灭)
		  LED_2(0);//LED2灯控制(1点亮,0熄灭)
	  }

}

1.闪灯 

led文件夹  

led.h

#ifndef LED_LED_H_
#define LED_LED_H_

#include "stm32f1xx_hal.h" //HAL库文件声明
#include "main.h" //IO定义与初始化函数在main.c文件中,必须引用

void LED_1(uint8_t a);//LED1独立控制函数(0为熄灭,其他值为点亮)
void LED_2(uint8_t a);//LED2独立控制函数(0为熄灭,其他值为点亮)
void LED_ALL(uint8_t a);//LED1~4整组操作函数(低4位的1/0状态对应4个LED亮灭,最低位对应LED1)
void LED_1_Contrary(void);//LED1状态取反
void LED_2_Contrary(void);//LED2状态取反

#endif /* LED_LED_H_ */

led.c 

#include "led.h"


void LED_1(uint8_t a)//LED1独立控制函数(0为熄灭,其他值为点亮)
{
	if(a)HAL_GPIO_WritePin(GPIOB,LED1_Pin,GPIO_PIN_SET);
	else HAL_GPIO_WritePin(GPIOB,LED1_Pin,GPIO_PIN_RESET);
}

void LED_2(uint8_t a)//LED2独立控制函数(0为熄灭,其他值为点亮)
{
	if(a)HAL_GPIO_WritePin(GPIOB,LED2_Pin,GPIO_PIN_SET);
	else HAL_GPIO_WritePin(GPIOB,LED2_Pin,GPIO_PIN_RESET);
}

void LED_ALL(uint8_t a)//LED1~2整组操作函数(低2位的1/0状态对应2个LED亮灭,最低位对应LED1)
{
	if(a&0x01)HAL_GPIO_WritePin(GPIOB,LED1_Pin,GPIO_PIN_SET);
	else HAL_GPIO_WritePin(GPIOB,LED1_Pin,GPIO_PIN_RESET);
	if(a&0x02)HAL_GPIO_WritePin(GPIOB,LED2_Pin,GPIO_PIN_SET);
	else HAL_GPIO_WritePin(GPIOB,LED2_Pin,GPIO_PIN_RESET);
}

void LED_1_Contrary(void){
	HAL_GPIO_WritePin(GPIOB,LED1_Pin,1-HAL_GPIO_ReadPin(GPIOB,LED1_Pin));
}

void LED_2_Contrary(void){
	HAL_GPIO_WritePin(GPIOB,LED2_Pin,1-HAL_GPIO_ReadPin(GPIOB,LED2_Pin));
}

2.按键控制LED亮灭 

key文件夹 

key.h 

#ifndef KEY_KEY_H_
#define KEY_KEY_H_

#include "stm32f1xx_hal.h" //HAL库文件声明
#include "main.h" //IO定义与初始化函数在main.c文件中,必须引用

uint8_t KEY_1(void);//按键1
uint8_t KEY_2(void);//按键2

#endif /* KEY_KEY_H_ */

key.c

#include "key.h"

uint8_t KEY_1(void)
{
	uint8_t a;
	a=0;//如果未进入按键处理,则返回0
	if(HAL_GPIO_ReadPin(GPIOA,KEY1_Pin)==GPIO_PIN_RESET){//读按键接口的电平
		HAL_Delay(20);//延时去抖动
		if(HAL_GPIO_ReadPin(GPIOA,KEY1_Pin)==GPIO_PIN_RESET){ //读按键接口的电平
			a=1;//进入按键处理,返回1
		}
	}
	while(HAL_GPIO_ReadPin(GPIOA,KEY1_Pin)==GPIO_PIN_RESET); //等待按键松开
	return a;
}

uint8_t KEY_2(void)
{
	uint8_t a;
	a=0;//如果未进入按键处理,则返回0
	if(HAL_GPIO_ReadPin(GPIOA,KEY2_Pin)==GPIO_PIN_RESET){//读按键接口的电平
		HAL_Delay(20);//延时去抖动
		if(HAL_GPIO_ReadPin(GPIOA,KEY2_Pin)==GPIO_PIN_RESET){ //读按键接口的电平
			a=1;//进入按键处理,返回1
		}
	}
	while(HAL_GPIO_ReadPin(GPIOA,KEY2_Pin)==GPIO_PIN_RESET); //等待按键松开
	return a;
}

二:蜂鸣器与继电器驱动程序

main.c

#include "main.h"
#include "../../icode/led/led.h"
#include "../../icode/key/key.h"

#include "../../icode/delay/delay.h"
#include "../../icode/buzzer/buzzer.h"

#include "../../icode/relay/relay.h"


int main(void)
{

	  if(KEY_1()) //按键KEY1判断为1时按键按下
	  {
		  LED_1(1);//LED1灯控制(1点亮,0熄灭)
		  LED_2(1);//LED2灯控制(1点亮,0熄灭)

		  BUZZER_SOLO1();//蜂鸣器输出单音的报警音(样式1:HAL库的精准延时函数)

		  RELAY_1(1);//继电器的控制程序(c=0继电器放开,c=1继电器吸合)
	  }
	  if(KEY_2()) //按键KEY2判断为1时按键按下
	  {
		  LED_1(0);//LED1灯控制(1点亮,0熄灭)
		  LED_2(0);//LED2灯控制(1点亮,0熄灭)

		  BUZZER_SOLO2();//蜂鸣器输出单音的报警音(样式2:CPU微秒级延时)

		  RELAY_1(0);//继电器的控制程序(c=0继电器放开,c=1继电器吸合)
	  }

}

1.蜂鸣器

buzzer文件夹 

PB5    n/a    High    output Push Pull    High    BEEP1

buzzer.h

#ifndef BUZZER_BUZZER_H_
#define BUZZER_BUZZER_H_

#include "stm32f1xx_hal.h" //HAL库文件声明
#include "main.h"
#include "../delay/delay.h"
void BUZZER_SOLO1(void);
void BUZZER_SOLO2(void);


#endif /* BUZZER_BUZZER_H_ */

buzzer.c

#include "buzzer.h"

#define time1 50 //单音的时长
#define hz1 1 //单音的音调(单位毫秒)


void BUZZER_SOLO1(void){//蜂鸣器输出单音的报警音(样式1:HAL库的精准延时函数)
    uint16_t i;
    for(i=0;i

delay文件夹 

delay.h

#ifndef DELAY_DELAY_H_
#define DELAY_DELAY_H_

#include "stm32f1xx_hal.h" //HAL库文件声明
void delay_us(uint32_t us); //C文件中的函数声明

#endif /* DELAY_DELAY_H_ */

delay.c

#include "delay.h"

void delay_us(uint32_t us) //利用CPU循环实现的非精准应用的微秒延时函数
{
    uint32_t delay = (HAL_RCC_GetHCLKFreq() / 8000000 * us); //使用HAL_RCC_GetHCLKFreq()函数获取主频值,经算法得到1微秒的循环次数
    while (delay--); //循环delay次,达到1微秒延时
}

2.继电器

relay文件夹 

relay.h

#ifndef INC_RELAY_H_
#define INC_RELAY_H_

//继电器接口定义与初始化函数在MX中设置并生成在main.c文件中
#include "stm32f1xx_hal.h" //HAL库文件声明
#include "main.h" //IO定义与初始化函数在main.c文件中,必须引用

void RELAY_1(uint8_t c);//继电器控制1

#endif /* INC_RELAY_H_ */

relay.c

#include "relay.h"

void RELAY_1(uint8_t c){ //继电器的控制程序(c=0继电器放开,c=1继电器吸合)
    if(c)HAL_GPIO_WritePin(GPIOA,RELAY1_Pin,GPIO_PIN_RESET); //继电器吸
    else  HAL_GPIO_WritePin(GPIOA,RELAY1_Pin,GPIO_PIN_SET); //继电器松
}

三:USART串口收发测试程序(超级终端)

syscalls.c文件与添加的内容冲突(发送信号):所有先禁止此文件参与编译后,再加入我们新的文件 

Core文件夹——>Src文件夹——>syscalls.c文件——>鼠标右击属性——>左侧点击C/C++ Build——>右侧勾选Exclude resource from build——>应用并关闭

接收信号 

开启USART1串口接收中断:打开CubeMX图形界面——>Connectivity——>USART1——>NMC Settings——>勾选USART1 global interrupt

main.c

#include "main.h"
#include "../../icode/led/led.h"
#include "../../icode/key/key.h"
#include "../../icode/delay/delay.h"
#include "../../icode/buzzer/buzzer.h"
#include "../../icode/relay/relay.h"

#include "../inc/retarget.h"            //用于printf函数串口重映射
#include "../../icode/usart/usart.h"    //串口接收


RetargetInit(&huart1);                                        //将printf()函数映射到UART1串口上
HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1);    //开启串口1接收中断



MX_ADC1_Init();
MX_ADC2_Init();



  while (1)
  {
	  if(KEY_1()) //按键KEY1判断为1时按键按下
	  {
		  LED_1(1);//LED1灯控制(1点亮,0熄灭)
		  LED_2(1);//LED2灯控制(1点亮,0熄灭)
		  BUZZER_SOLO1();//蜂鸣器输出单音的报警音(样式1:HAL库的精准延时函数)
		  RELAY_1(1);//继电器的控制程序(c=0继电器放开,c=1继电器吸合)

	      HAL_UART_Transmit(&huart1,(uint8_t*)&"KEY1\r\n",6,0xffff);//串口发送:串口号1,内容"ABC",数量3,溢出时间0xffff
	  }
	  if(KEY_2()) //按键KEY2判断为1时按键按下
	  {
		  LED_1(0);//LED1灯控制(1点亮,0熄灭)
		  LED_2(0);//LED2灯控制(1点亮,0熄灭)
		  BUZZER_SOLO2();//蜂鸣器输出单音的报警音(样式2:CPU微秒级延时)
		  RELAY_1(0);//继电器的控制程序(c=0继电器放开,c=1继电器吸合)

		  printf("KEY2\r\n");//向USART1串口发送字符串
	  }


     //键盘输入“1+回车”或“O+回车”                               键盘输入“1”或“0”
	  if(USART1_RX_STA & 0xc000){//串口1判断中断接收标志位    USART1_RX_STA==0x0001
		  if(USART1_RX_BUF[0]=='1'){
			  LED_1(1);//LED1灯控制(1点亮,0熄灭)
			  LED_2(1);//LED2灯控制(1点亮,0熄灭)
			  BUZZER_SOLO1();//蜂鸣器输出单音的报警音(样式1:HAL库的精准延时函数)
			  RELAY_1(1);//继电器的控制程序(c=0继电器放开,c=1继电器吸合)
		  }
		  if(USART1_RX_BUF[0]=='0'){
			  LED_1(0);//LED1灯控制(1点亮,0熄灭)
			  LED_2(0);//LED2灯控制(1点亮,0熄灭)
			  BUZZER_SOLO2();//蜂鸣器输出单音的报警音(样式2:CPU微秒级延时)
			  RELAY_1(0);//继电器的控制程序(c=0继电器放开,c=1继电器吸合)
		  }
		  USART1_RX_STA=0;//串口接收标志清0,即开启下一轮接收
	  }


}

...\Core\Src下新建 retarget.c文件

...\Core\Inc下新建 retarget.h文件

retarget.h

#ifndef INC_RETARGET_H_
#define INC_RETARGET_H_

#include "stm32f1xx_hal.h"
#include "stdio.h"//用于printf函数串口重映射
#include 

void RetargetInit(UART_HandleTypeDef  *huart);

int _isatty(int fd);
int _write(int fd, char* ptr, int len);
int _close(int fd);
int _lseek(int fd, int ptr, int dir);
int _read(int fd, char* ptr, int len);
int _fstat(int fd, struct stat* st);

#endif /* INC_RETARGET_H_ */

retarget.c

#include <_ansi.h>
#include <_syslist.h>
#include 
#include 
#include 
#include 
#include 
#include <../Inc/retarget.h>
#include 
#include 
#if !defined(OS_USE_SEMIHOSTING)
#define STDIN_FILENO  0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2

UART_HandleTypeDef *gHuart;

void RetargetInit(UART_HandleTypeDef *huart)  {
  gHuart = huart;
  /* Disable I/O buffering for STDOUT  stream, so that
   * chars are sent out as soon as they are  printed. */
  setvbuf(stdout, NULL, _IONBF, 0);
}
int _isatty(int fd) {
  if (fd >= STDIN_FILENO && fd <=  STDERR_FILENO)
    return 1;
  errno = EBADF;
  return 0;
}
int _write(int fd, char* ptr, int len) {
  HAL_StatusTypeDef hstatus;
  if (fd == STDOUT_FILENO || fd ==  STDERR_FILENO) {
    hstatus = HAL_UART_Transmit(gHuart,  (uint8_t *) ptr, len, HAL_MAX_DELAY);
    if (hstatus == HAL_OK)
      return len;
    else
      return EIO;
  }
  errno = EBADF;
  return -1;
}
int _close(int fd) {
  if (fd >= STDIN_FILENO && fd <=  STDERR_FILENO)
    return 0;
  errno = EBADF;
  return -1;
}
int _lseek(int fd, int ptr, int dir) {
  (void) fd;
  (void) ptr;
  (void) dir;
  errno = EBADF;
  return -1;
}
int _read(int fd, char* ptr, int len) {
  HAL_StatusTypeDef hstatus;
  if (fd == STDIN_FILENO) {
    hstatus = HAL_UART_Receive(gHuart,  (uint8_t *) ptr, 1, HAL_MAX_DELAY);
    if (hstatus == HAL_OK)
      return 1;
    else
      return EIO;
  }
  errno = EBADF;
  return -1;
}
int _fstat(int fd, struct stat* st) {
  if (fd >= STDIN_FILENO && fd <=  STDERR_FILENO) {
    st->st_mode = S_IFCHR;
    return 0;
  }
  errno = EBADF;
  return 0;
}

#endif //#if !defined(OS_USE_SEMIHOSTING)

usart文件夹

usart.h

#ifndef INC_USART_H_
#define INC_USART_H_

#include "stm32f1xx_hal.h" //HAL库文件声明
#include //用于字符串处理的库
#include "../inc/retarget.h"//用于printf函数串口重映射

extern UART_HandleTypeDef huart1;//声明USART1的HAL库结构体
extern UART_HandleTypeDef huart2;//声明USART2的HAL库结构体
extern UART_HandleTypeDef huart3;//声明USART2的HAL库结构体

#define USART1_REC_LEN  200//定义USART1最大接收字节数
#define USART2_REC_LEN  200//定义USART1最大接收字节数
#define USART3_REC_LEN  200//定义USART1最大接收字节数

extern uint8_t  USART1_RX_BUF[USART1_REC_LEN];//接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern uint16_t USART1_RX_STA;//接收状态标记
extern uint8_t USART1_NewData;//当前串口中断接收的1个字节数据的缓存

extern uint8_t  USART2_RX_BUF[USART2_REC_LEN];//接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern uint16_t USART2_RX_STA;//接收状态标记
extern uint8_t USART2_NewData;//当前串口中断接收的1个字节数据的缓存
extern uint8_t RS485orBT;//当RS485orBT标志位为1时是RS485模式,为0时是蓝牙模式

extern uint8_t  USART3_RX_BUF[USART3_REC_LEN];//接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern uint16_t USART3_RX_STA;//接收状态标记
extern uint8_t USART3_NewData;//当前串口中断接收的1个字节数据的缓存

void  HAL_UART_RxCpltCallback(UART_HandleTypeDef  *huart);//串口中断回调函数声明

#endif /* INC_USART_H_ */

usart.c

#include "usart.h"

uint8_t USART1_RX_BUF[USART1_REC_LEN];//接收缓冲,最大USART_REC_LEN个字节.
uint16_t USART1_RX_STA=0;//接收状态标记//bit15:接收完成标志,bit14:接收到0x0d,bit13~0:接收到的有效字节数目
uint8_t USART1_NewData;//当前串口中断接收的1个字节数据的缓存

uint8_t USART2_RX_BUF[USART2_REC_LEN];//接收缓冲,最大USART_REC_LEN个字节.
uint16_t USART2_RX_STA=0;//接收状态标记//bit15:接收完成标志,bit14:接收到0x0d,bit13~0:接收到的有效字节数目
uint8_t USART2_NewData;//当前串口中断接收的1个字节数据的缓存
uint8_t RS485orBT;//当RS485orBT标志位为1时是RS485模式,为0时是蓝牙模式

uint8_t USART3_RX_BUF[USART3_REC_LEN];//接收缓冲,最大USART_REC_LEN个字节.
uint16_t USART3_RX_STA=0;//接收状态标记//bit15:接收完成标志,bit14:接收到0x0d,bit13~0:接收到的有效字节数目
uint8_t USART3_NewData;//当前串口中断接收的1个字节数据的缓存

void  HAL_UART_RxCpltCallback(UART_HandleTypeDef  *huart)//串口中断回调函数
{
	if(huart ==&huart1)//判断中断来源(串口1:USB转串口)
    {
       printf("%c",USART1_NewData); //把收到的数据以 a符号变量 发送回电脑
       if((USART1_RX_STA&0x8000)==0){//接收未完成
           if(USART1_RX_STA&0x4000){//接收到了0x0d
               if(USART1_NewData!=0x0a)USART1_RX_STA=0;//接收错误,重新开始
               else USART1_RX_STA|=0x8000;   //接收完成了
           }else{ //还没收到0X0D
               if(USART1_NewData==0x0d)USART1_RX_STA|=0x4000;
               else{
                  USART1_RX_BUF[USART1_RX_STA&0X3FFF]=USART1_NewData; //将收到的数据放入数组
                  USART1_RX_STA++;  //数据长度计数加1
                  if(USART1_RX_STA>(USART1_REC_LEN-1))USART1_RX_STA=0;//接收数据错误,重新开始接收
               }
           }
       }
       HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1); //再开启接收中断
    }
    if(huart ==&huart2)//判断中断来源(RS485/蓝牙模块)
    {
       if(RS485orBT){//当RS485orBT标志位为1时是RS485模式,为0时是蓝牙模式
    	   USART2_RX_BUF[0]=USART2_NewData;//将接收到的数据放入缓存数组(因只用到1个数据,所以只存放在数据[0]位置)
    	   USART2_RX_STA++;//数据接收标志位加1
       }else{
    	   printf("%c",USART2_NewData); //把收到的数据以 a符号变量 发送回电脑
       }
       HAL_UART_Receive_IT(&huart2,(uint8_t *)&USART2_NewData, 1); //再开启接收中断
    }
	if(huart ==&huart3)//判断中断来源(串口3:WIFI模块)
	{
		printf("%c",USART3_NewData); //把收到的数据以 a符号变量 发送回电脑
		HAL_UART_Receive_IT(&huart3,(uint8_t *)&USART3_NewData,1); //再开启接收中断
	}
}

四:ADC与DMA驱动程序

1.ADC读取电位器与光敏电阻测试程序(通过DMA驱动函数的方式,用时长) 

逻辑电平输入:只能读到高、低电平(1或0)


ADC输入:可读出区间内(0~3.3v)的电压值

设置 

CubeMX的设置——>时钟配置窗口——>可设置ADC时钟的分频系数ADC Prescaler为8
                          ——>设置ADC时钟最终频率To ADC1,2为9


端口视图的设置——>PA4端口的模式设置为ADC1_IN4
             ——>PA5端口的模式设置为ADC2_IN5


Analog功能组中——>ADC1功能——>确定IN4是否被自动勾选
              ——>ADC2功能——>确定IN5是否被自动勾选


在参数选项卡中——>将Rank第1组中的转换时间设置为55.5个时钟周期



main.c

#include "main.h"
#include "../../icode/led/led.h"
#include "../../icode/key/key.h"
#include "../../icode/delay/delay.h"
#include "../../icode/buzzer/buzzer.h"
#include "../../icode/relay/relay.h"
#include "../inc/retarget.h"//用于printf函数串口重映射
#include "../../icode/usart/usart.h"

#include "../../icode/adc/adc.h"



int main(void)
{
    uint16_t a1,a2;//用于ADC数据读取的暂时变量


    RetargetInit(&huart1);//将printf()函数映射到UART1串口上
    HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1);//开启串口1接收中断
    
    //在ADC开始转换之前先校转换电路,使得后续转换得出的值更精确
    HAL_ADCEx_Calibration_Start(&hadc1);//ADC采样校准



    	  if(KEY_1()) //按键KEY1判断为1时按键按下
	  {
		  LED_1(1);//LED1灯控制(1点亮,0熄灭)
		  LED_2(1);//LED2灯控制(1点亮,0熄灭)
		  BUZZER_SOLO1();//蜂鸣器输出单音的报警音(样式1:HAL库的精准延时函数)
		  RELAY_1(1);//继电器的控制程序(c=0继电器放开,c=1继电器吸合)
	      HAL_UART_Transmit(&huart1,(uint8_t*)&"KEY1\r\n",6,0xffff);//串口发送:串口号1,内容"ABC",数量3,溢出时间0xffff
	  }
	  if(KEY_2()) //按键KEY2判断为1时按键按下
	  {
		  LED_1(0);//LED1灯控制(1点亮,0熄灭)
		  LED_2(0);//LED2灯控制(1点亮,0熄灭)
		  BUZZER_SOLO2();//蜂鸣器输出单音的报警音(样式2:CPU微秒级延时)
		  RELAY_1(0);//继电器的控制程序(c=0继电器放开,c=1继电器吸合)
		  printf("KEY2\r\n");//向USART1串口发送字符串
	  }
	  if(USART1_RX_STA==0x0001){//串口1判断中断接收标志位
		  if(USART1_RX_BUF[0]=='1'){
			  LED_1(1);//LED1灯控制(1点亮,0熄灭)
			  LED_2(1);//LED2灯控制(1点亮,0熄灭)
			  BUZZER_SOLO1();//蜂鸣器输出单音的报警音(样式1:HAL库的精准延时函数)
			  RELAY_1(1);//继电器的控制程序(c=0继电器放开,c=1继电器吸合)
		  }
		  if(USART1_RX_BUF[0]=='0'){
			  LED_1(0);//LED1灯控制(1点亮,0熄灭)
			  LED_2(0);//LED2灯控制(1点亮,0熄灭)
			  BUZZER_SOLO2();//蜂鸣器输出单音的报警音(样式2:CPU微秒级延时)
			  RELAY_1(0);//继电器的控制程序(c=0继电器放开,c=1继电器吸合)
		  }
		  USART1_RX_STA=0;//串口接收标志清0,即开启下一轮接收
	  }


      a1 =  ADC_IN_1();//读出ADC1数值(电位器)
      a2 =  ADC_IN_2();//读出ADC2数值(光敏电阻)

      //强制以4位输出
      printf("ADC1=%04d  ADC2=%04d \r\n",a1,a2);//向USART1串口发送字符串
      HAL_Delay(500);//在主循环里写入HAL库的毫秒级延时函数
}

adc文件夹

adc.h

#ifndef ADC_ADC_H_
#define ADC_ADC_H_

#include "stm32f1xx_hal.h" //HAL库文件声明
extern ADC_HandleTypeDef hadc1;
extern ADC_HandleTypeDef hadc2;

uint16_t ADC_IN_1(void);
uint16_t ADC_IN_2(void);

#endif /* ADC_ADC_H_ */

adc.c

#include "adc.h"

uint16_t ADC_IN_1(void) //ADC采集程序
{
	HAL_ADC_Start(&hadc1);//开始ADC采集
	HAL_ADC_PollForConversion(&hadc1,500);//等待采集结束
	if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1), HAL_ADC_STATE_REG_EOC))//读取ADC完成标志位
	{
		return HAL_ADC_GetValue(&hadc1);//读出ADC数值
	}
	return 0;
}

uint16_t ADC_IN_2(void) //ADC采集程序
{
	HAL_ADC_Start(&hadc2);//开始ADC采集
	HAL_ADC_PollForConversion(&hadc2,500);//等待采集结束
	if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc2), HAL_ADC_STATE_REG_EOC))//读取ADC完成标志位
	{
		return HAL_ADC_GetValue(&hadc2);//读出ADC数值
	}
	return 0;
}

2.DMA 

2.1 用DMA读取单路ADC测试程序 

设置

Analog功能组中——>ADC1功能1——>左下方NMC Settings——>勾选DMA1的中断允许
              ——>ADC1功能1——>DMA Settings——>点击Add——>选择ADC1
                                                   ——>右边设置为Hight
                                                   ——>选择循环模式Mode——>Circular
                                                   ——>勾选Memory寄存器
                                                   ——>选择"半字"数据宽度Half Word


在参数选项卡中——>Continuous Conmversion Mode——>Enabled开启连续转换模式


CubeMX的设置——>工程管理选项卡Project Manager——>点击高级子选项卡Advanced Settings——>选择DMA一行——>点击列表右上方的排序上移

main.c

#include "main.h"
#include "../../icode/led/led.h"
#include "../../icode/key/key.h"
#include "../../icode/delay/delay.h"
#include "../../icode/buzzer/buzzer.h"
#include "../../icode/relay/relay.h"
#include "../inc/retarget.h"//用于printf函数串口重映射
#include "../../icode/usart/usart.h"
#include "../../icode/adc/adc.h"


void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_RTC_Init(void);

static void MX_DMA_Init(void);

MX_ADC1_Init();
MX_CAN_Init();
MX_SPI2_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
MX_USART3_UART_Init();
MX_USB_PCD_Init();
MX_ADC2_Init();


int main(void)
{
  RetargetInit(&huart1);//将printf()函数映射到UART1串口上
  HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1);//开启串口1接收中断
  HAL_ADCEx_Calibration_Start(&hadc1);//ADC采样校准
  HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&a1,1);//启动DMA。传递的功能,传递的数据,传递的数据长度


   while (1)
  {

	  if(KEY_1()) //按键KEY1判断为1时按键按下
	  {
		  LED_1(1);//LED1灯控制(1点亮,0熄灭)
		  LED_2(1);//LED2灯控制(1点亮,0熄灭)
		  BUZZER_SOLO1();//蜂鸣器输出单音的报警音(样式1:HAL库的精准延时函数)
		  RELAY_1(1);//继电器的控制程序(c=0继电器放开,c=1继电器吸合)
	      HAL_UART_Transmit(&huart1,(uint8_t*)&"KEY1\r\n",6,0xffff);//串口发送:串口号1,内容"ABC",数量3,溢出时间0xffff
	  }
	  if(KEY_2()) //按键KEY2判断为1时按键按下
	  {
		  LED_1(0);//LED1灯控制(1点亮,0熄灭)
		  LED_2(0);//LED2灯控制(1点亮,0熄灭)
		  BUZZER_SOLO2();//蜂鸣器输出单音的报警音(样式2:CPU微秒级延时)
		  RELAY_1(0);//继电器的控制程序(c=0继电器放开,c=1继电器吸合)
		  printf("KEY2\r\n");//向USART1串口发送字符串
	  }
	  if(USART1_RX_STA==0x0001){//串口1判断中断接收标志位
		  if(USART1_RX_BUF[0]=='1'){
			  LED_1(1);//LED1灯控制(1点亮,0熄灭)
			  LED_2(1);//LED2灯控制(1点亮,0熄灭)
			  BUZZER_SOLO1();//蜂鸣器输出单音的报警音(样式1:HAL库的精准延时函数)
			  RELAY_1(1);//继电器的控制程序(c=0继电器放开,c=1继电器吸合)
		  }
		  if(USART1_RX_BUF[0]=='0'){
			  LED_1(0);//LED1灯控制(1点亮,0熄灭)
			  LED_2(0);//LED2灯控制(1点亮,0熄灭)
			  BUZZER_SOLO2();//蜂鸣器输出单音的报警音(样式2:CPU微秒级延时)
			  RELAY_1(0);//继电器的控制程序(c=0继电器放开,c=1继电器吸合)
		  }
		  USART1_RX_STA=0;//串口接收标志清0,即开启下一轮接收
	  }
      //a1 =  ADC_IN_1();//读出ADC1数值(电位器)    由于ADC1已经改用DMA读取
      a2 =  ADC_IN_2();//读出ADC2数值(光敏电阻)
      printf("ADC1=%04d  ADC2=%04d \r\n",a1,a2);//向USART1串口发送字符串
      HAL_Delay(500);//在主循环里写入HAL库的毫秒级延时函数
}

2.2 用DMA读取多路ADC测试程序

设置

因为ADC2不支持DMA,我们只能把ADC2的通道5改成ADC1的通道5,然后在ADC1里用DMA循环交替读通道4和通道5的数值
    在单片机端口初视图中点晶PA5——>点击ADC2_IN5取消选择
                              ——>点击ADC1_IN5——>重新选择为ADC1_IN5


Analog功能组中——>ADC1功能1——>Parameter Settings——>Number of Conversion——>2将通道数里设置为2        
                                                                             ——>Rank1——>Channel 4
                                                                             ——>Rank2——>Channel 5

main.c

#include "main.h"
#include "../../icode/led/led.h"
#include "../../icode/key/key.h"
#include "../../icode/delay/delay.h"
#include "../../icode/buzzer/buzzer.h"
#include "../../icode/relay/relay.h"
#include "../inc/retarget.h"//用于printf函数串口重映射
#include "../../icode/usart/usart.h"
#include "../../icode/adc/adc.h"


int main(void)
{

    uint16_t dmaadc[2];//用于多路ADC数据读取的暂时数组

    RetargetInit(&huart1);//将printf()函数映射到UART1串口上
    HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1);//开启串口1接收中断
    HAL_ADCEx_Calibration_Start(&hadc1);//ADC采样校准
    HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&dmaadc,2);//启动DMA,采集数据存入的变量地址,长度


while (1)
  {
	  if(KEY_1()) //按键KEY1判断为1时按键按下
	  {
		  LED_1(1);//LED1灯控制(1点亮,0熄灭)
		  LED_2(1);//LED2灯控制(1点亮,0熄灭)
		  BUZZER_SOLO1();//蜂鸣器输出单音的报警音(样式1:HAL库的精准延时函数)
		  RELAY_1(1);//继电器的控制程序(c=0继电器放开,c=1继电器吸合)
	      HAL_UART_Transmit(&huart1,(uint8_t*)&"KEY1\r\n",6,0xffff);//串口发送:串口号1,内容"ABC",数量3,溢出时间0xffff
	  }
	  if(KEY_2()) //按键KEY2判断为1时按键按下
	  {
		  LED_1(0);//LED1灯控制(1点亮,0熄灭)
		  LED_2(0);//LED2灯控制(1点亮,0熄灭)
		  BUZZER_SOLO2();//蜂鸣器输出单音的报警音(样式2:CPU微秒级延时)
		  RELAY_1(0);//继电器的控制程序(c=0继电器放开,c=1继电器吸合)
		  printf("KEY2\r\n");//向USART1串口发送字符串
	  }
	  if(USART1_RX_STA==0x0001){//串口1判断中断接收标志位
		  if(USART1_RX_BUF[0]=='1'){
			  LED_1(1);//LED1灯控制(1点亮,0熄灭)
			  LED_2(1);//LED2灯控制(1点亮,0熄灭)
			  BUZZER_SOLO1();//蜂鸣器输出单音的报警音(样式1:HAL库的精准延时函数)
			  RELAY_1(1);//继电器的控制程序(c=0继电器放开,c=1继电器吸合)
		  }
		  if(USART1_RX_BUF[0]=='0'){
			  LED_1(0);//LED1灯控制(1点亮,0熄灭)
			  LED_2(0);//LED2灯控制(1点亮,0熄灭)
			  BUZZER_SOLO2();//蜂鸣器输出单音的报警音(样式2:CPU微秒级延时)
			  RELAY_1(0);//继电器的控制程序(c=0继电器放开,c=1继电器吸合)
		  }
		  USART1_RX_STA=0;//串口接收标志清0,即开启下一轮接收
	  }

//      a1 =  ADC_IN_1();//读出ADC1数值(电位器)
//      a2 =  ADC_IN_2();//读出ADC2数值(光敏电阻)

      printf("ADC1=%04d  ADC2=%04d \r\n",dmaadc[0],dmaadc[1]);//向USART1串口发送字符串    通道4和通道5
      HAL_Delay(500);//在主循环里写入HAL库的毫秒级延时函数
}

五:RTC与BKP驱动程序

1.HAL库自带的RTC时钟测试程序

设置

开启RTC功能
    打开时钟数视图——>RTC的时钟这里选择LSE外部低速时钟LSE——>频率为32.7⑥8KHz
    Timers——>RTC——>勾选Activate Clock Source激活时钟    Activate Calendar激活日历
                ——>Parameter Settings——>按默认设置

main.c

#include "main.h"
#include "../../icode/led/led.h"
#include "../../icode/key/key.h"
#include "../../icode/delay/delay.h"
#include "../../icode/buzzer/buzzer.h"
#include "../../icode/relay/relay.h"
#include "../inc/retarget.h"//用于printf函数串口重映射
#include "../../icode/usart/usart.h"


int main(void)
{

    RTC_DateTypeDef RtcDate;
    RTC_TimeTypeDef RtcTime;
    

     while (1)
      {
	    if(USART1_RX_STA&0xC000){ //如果标志位是0xC000表示收到数据串完成,可以处理。
	       if((USART1_RX_STA&0x3FFF)==0){ //单独的回车键再显示一次欢迎词
	           HAL_RTC_GetTime(&hrtc, &RtcTime,  RTC_FORMAT_BIN);//读出时间值
	           HAL_RTC_GetDate(&hrtc, &RtcDate,  RTC_FORMAT_BIN);//一定要先读时间后读日期
	           printf(" 洋桃IoT开发板RTC实时时钟测试   \r\n");
	           printf(" 实时时间:%04d-%02d-%02d  %02d:%02d:%02d  \r\n",2000+RtcDate.Year,
	        		   RtcDate.Month, RtcDate.Date,RtcTime.Hours, RtcTime.Minutes, RtcTime.Seconds);//显示日期时间
	           printf(" 单按回车键更新时间,输入字母C初始化时钟 \r\n");
	           printf(" 请输入设置时间,格式20170806120000,按回车键确定! \r\n");
	       }else if((USART1_RX_STA&0x3FFF)==1){  //判断数据是不是1个
	           if(USART1_RX_BUF[0]=='c' ||  USART1_RX_BUF[0]=='C'){
	               MX_RTC_Init(); //键盘输入c或C,初始化时钟
	               printf("初始化成功!       \r\n");//显示初始化成功
	           }else{
	               printf("指令错误!           \r\n"); //显示指令错误!
	           }
	       }else  if((USART1_RX_STA&0x3FFF)==14){ //判断数据是不是14个
	           //将超级终端发过来的数据换算并写入RTC
	           RtcDate.Year =  (USART1_RX_BUF[2]-0x30)*10+USART1_RX_BUF[3]-0x30;//减0x30后才能得到十进制0~9的数据
	           RtcDate.Month =  (USART1_RX_BUF[4]-0x30)*10+USART1_RX_BUF[5]-0x30;
	           RtcDate.Date =  (USART1_RX_BUF[6]-0x30)*10+USART1_RX_BUF[7]-0x30;
	           RtcTime.Hours =  (USART1_RX_BUF[8]-0x30)*10+USART1_RX_BUF[9]-0x30;
	           RtcTime.Minutes =  (USART1_RX_BUF[10]-0x30)*10+USART1_RX_BUF[11]-0x30;
	           RtcTime.Seconds =  (USART1_RX_BUF[12]-0x30)*10+USART1_RX_BUF[13]-0x30;
	           if (HAL_RTC_SetTime(&hrtc,  &RtcTime, RTC_FORMAT_BIN) != HAL_OK)//将数据写入RTC程序
	           {
	               printf("写入时间失败!        \r\n"); //显示写入失败
	           }else if (HAL_RTC_SetDate(&hrtc,  &RtcDate, RTC_FORMAT_BIN) != HAL_OK)//将数据写入RTC程序
	           {
	               printf("写入日期失败!        \r\n"); //显示写入失败
	           }else printf("写入成功!       \r\n");//显示写入成功
	       }else{ //如果以上都不是,即是错误的指令。
	           printf("指令错误!          \r\n");  //如果不是以上正确的操作,显示指令错误!
	       }
	       USART1_RX_STA=0; //将串口数据标志位清0
	    }
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }



    static void MX_RTC_Init(void)
    {

      RTC_TimeTypeDef sTime = {0};
      RTC_DateTypeDef DateToUpdate = {0};

      __HAL_RCC_PWR_CLK_ENABLE();//使能电源时钟PWR
      HAL_PWR_EnableBkUpAccess();//取消备份区域写保护

      hrtc.Instance = RTC;
      hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
      hrtc.Init.OutPut = RTC_OUTPUTSOURCE_ALARM;
      if (HAL_RTC_Init(&hrtc) != HAL_OK)
      {
        Error_Handler();
      }

    
    if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!=0X5050)//判断是否首次上电
	{
      HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0X5050); //标记数值(写入上电检查数值)

      sTime.Hours = 0x0;
      sTime.Minutes = 0x0;
      sTime.Seconds = 0x0;

      if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
      {
        Error_Handler();
      }
      DateToUpdate.WeekDay = RTC_WEEKDAY_MONDAY;
      DateToUpdate.Month = RTC_MONTH_JANUARY;
      DateToUpdate.Date = 0x1;
      DateToUpdate.Year = 0x0;

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

}

2.创建走时完善的RTC时钟测试程序

BKP(寄存器 可以自由存放数据):在单片机断电后依然保证RTC特续走时,利用备用电池

设置

禁用RTC初始化函数(不然初始化会删除正常走时的日期和时阃)
    CubeMX的设置——>工程管理选项卡Project Manager——>点击高级子选项卡Advanced Settings——>选择DTEC一行——>勾选Do Not Generate Function Call不生成函数调用
        ——>取消勾选Static静态





CubeMX的设置——>时钟配置窗口——>可设置ADC时钟的分频系数ADC Prescaler为8
                          ——>设置ADC时钟最终频率To ADC1,2为9


端口视图的设置——>PA4端口的模式设置为ADC1_IN4
             ——>PA5端口的模式设置为ADC2_IN5


Analog功能组中——>ADC1功能——>确定IN4是否被自动勾选
              ——>ADC2功能——>确定IN5是否被自动勾选


在参数选项卡中——>将Rank第1组中的转换时间设置为55.5个时钟周期

main.c

#include "main.h"
#include "../../icode/led/led.h"
#include "../../icode/key/key.h"
#include "../../icode/delay/delay.h"
#include "../../icode/buzzer/buzzer.h"
#include "../../icode/relay/relay.h"
#include "../inc/retarget.h"//用于printf函数串口重映射
#include "../../icode/usart/usart.h"


  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_CAN_Init();
  MX_SPI2_Init();
  MX_USART1_UART_Init();
  MX_USART2_UART_Init();
  MX_USART3_UART_Init();
  MX_USB_PCD_Init();
  
  RetargetInit(&huart1);//将printf()函数映射到UART1串口上
  HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1);//开启串口1接收中断
  RTC_Init();//自创走时完善的RTC时钟初始化


int main(void)
{

   while (1)
  {
	if(USART1_RX_STA&0xC000){ //如果标志位是0xC000表示收到数据串完成,可以处理。
	   if((USART1_RX_STA&0x3FFF)==0){ //单独的回车键再显示一次欢迎词
		   RTC_Get();//读出当前RTC日期与时间,放入全局变量
		   printf(" 洋桃IoT开发板RTC实时时钟测试   \r\n");
		   printf(" 实时时间:%04d-%02d-%02d  %02d:%02d:%02d  \r\n",
				   ryear, rmon, rday, rhour, rmin, rsec);//显示日期时间
		   printf(" 单按回车键更新时间,输入字母C初始化时钟 \r\n");
		   printf(" 请输入设置时间,格式20170806120000,按回车键确定! \r\n");
	   }else if((USART1_RX_STA&0x3FFF)==1){  //判断数据是不是1个
		   if(USART1_RX_BUF[0]=='c' ||  USART1_RX_BUF[0]=='C'){
			   MX_RTC_Init(); //键盘输入c或C,初始化时钟(调用HAL库自带的初始化函数)
			   printf("初始化成功!       \r\n");//显示初始化成功
		   }else{
			   printf("指令错误!           \r\n"); //显示指令错误!
		   }
	   }else if((USART1_RX_STA&0x3FFF)==14){ //判断数据是不是14个
		   //将超级终端发过来的数据换算并写入RTC
		   ryear = (USART1_RX_BUF[0]-0x30)*1000 + (USART1_RX_BUF[1]-0x30)*100 +
				   (USART1_RX_BUF[2]-0x30)*10 + (USART1_RX_BUF[3]-0x30);//减0x30得到十进制0~9的数据
		   rmon =  (USART1_RX_BUF[4]-0x30)*10 + (USART1_RX_BUF[5]-0x30);
		   rday =  (USART1_RX_BUF[6]-0x30)*10 + (USART1_RX_BUF[7]-0x30);
		   rhour = (USART1_RX_BUF[8]-0x30)*10 + (USART1_RX_BUF[9]-0x30);
		   rmin =  (USART1_RX_BUF[10]-0x30)*10 + (USART1_RX_BUF[11]-0x30);
		   rsec =  (USART1_RX_BUF[12]-0x30)*10 + (USART1_RX_BUF[13]-0x30);
		   if (RTC_Set(ryear,rmon,rday,rhour,rmin,rsec) != HAL_OK)//将数据写入RTC程序
		   {
			   printf("写入时间失败!        \r\n"); //显示写入失败
		   }else printf("写入成功!       \r\n");//显示写入成功
	   }else{ //如果以上都不是,即是错误的指令。
		   printf("指令错误!          \r\n");  //如果不是以上正确的操作,显示指令错误!
	   }
	   USART1_RX_STA=0; //将串口数据标志位清0
	}


    void MX_RTC_Init(void)
    {

      RTC_TimeTypeDef sTime = {0};
      RTC_DateTypeDef DateToUpdate = {0};

      hrtc.Instance = RTC;
      hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
      hrtc.Init.OutPut = RTC_OUTPUTSOURCE_ALARM;
      if (HAL_RTC_Init(&hrtc) != HAL_OK)
      {
        Error_Handler();
      }

      sTime.Hours = 0x0;
      sTime.Minutes = 0x0;
      sTime.Seconds = 0x0;

      if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
      {
        Error_Handler();
      }
      DateToUpdate.WeekDay = RTC_WEEKDAY_MONDAY;
      DateToUpdate.Month = RTC_MONTH_JANUARY;
      DateToUpdate.Date = 0x1;
      DateToUpdate.Year = 0x0;

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

    }
}

rtc文件夹

rtc.h

#ifndef INC_RTC_H_
#define INC_RTC_H_

#include "stm32f1xx_hal.h" //HAL库文件声明
#include "main.h" //IO定义与初始化函数在main.c文件中,必须引用

/*
//时间读写与设置说明//
1,在mani.c文件中主循环之前放入RTC_Init();可使能RTC时钟。
	RTC_Init函数自带判断首次上电功能
2,使用RTC_Get();读出时间。读出的数据存放在:
年 ryear	(16位)
月 rmon	(以下都是8位)
日 rday
时 rhour
分 rmin
秒 rsec
周 rweek
3,使用RTC_Set(4位年,2位月,2位日,2位时,2位分,2位秒); 写入时间。
例如:RTC_Set(2022,8,6,21,34,0);

其他函数都是帮助如上3个函数的,不需要调用。
注意要使用RTC_Get和RTC_Set的返回值,为0时表示读写正确。
*/

extern RTC_HandleTypeDef hrtc;

//声明rtc.c文件中定义的全局变量(注意:这里不能给变量赋值)
extern uint16_t ryear;
extern uint8_t rmon,rday,rhour,rmin,rsec,rweek;

void RTC_Init(void); //用户自建的带有上电BPK判断的RTC初始化【在主循环前调用】
uint8_t Is_Leap_Year(uint16_t year);//判断是否是闰年函数
uint8_t RTC_Get(void);//读出当前时间值【主函数中需要读RTC时调用】
uint8_t RTC_Set(uint16_t syear,uint8_t smon,uint8_t sday,uint8_t hour,uint8_t min,uint8_t sec);//写入当前时间【主函数中需要写入RTC时调用】
uint8_t RTC_Get_Week(uint16_t year,uint8_t month,uint8_t day);//按年月日计算星期

#endif

rtc.c

#include "rtc.h"

//以下2行全局变量,用于RTC时间的读取与读入
uint16_t ryear; //4位年
uint8_t rmon,rday,rhour,rmin,rsec,rweek;//2位月日时分秒周

void RTC_Init(void) //用户自建的带有上电BPK判断的RTC初始化
{
  hrtc.Instance = RTC;
  hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
  hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
  if (HAL_RTC_Init(&hrtc) != HAL_OK)
  {
    Error_Handler();
  }
  if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!=0X5050){ //判断是否首次上电
	   HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0X5050); //标记数值 下次不执行“首次上电”的部分
	   RTC_Set(2022,1,1,0,0,0);//写入RTC时间的操作RTC_Set(4位年,2位月,2位日,2位时,2位分,2位秒)
  }
}

//判断是否是闰年函数
//月份   1  2  3  4  5  6  7  8  9  10 11 12
//闰年   31 29 31 30 31 30 31 31 30 31 30 31
//非闰年 31 28 31 30 31 30 31 31 30 31 30 31
//输入:年份
//输出:该年份是不是闰年.1,是.0,不是
uint8_t Is_Leap_Year(uint16_t year){
	if(year%4==0){ //必须能被4整除
		if(year%100==0){
			if(year%400==0)return 1;//如果以00结尾,还要能被400整除
			else return 0;
		}else return 1;
	}else return 0;
}
//设置时钟
//把输入的时钟转换为秒钟
//以1970年1月1日为基准
//1970~2099年为合法年份

//月份数据表
uint8_t const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表
const uint8_t mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};//平年的月份日期表

//写入时间
uint8_t RTC_Set(uint16_t syear,uint8_t smon,uint8_t sday,uint8_t hour,uint8_t min,uint8_t sec){ //写入当前时间(1970~2099年有效),
	uint16_t t;
	uint32_t seccount=0;
	if(syear<2000||syear>2099)return 1;//syear范围1970-2099,此处设置范围为2000-2099
	for(t=1970;tCRL|=1<<4;   //允许配置
	RTC->CNTL=seccount&0xffff;
	RTC->CNTH=seccount>>16;
	RTC->CRL&=~(1<<4);//配置更新
	while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成
	//【寄存器操作】结束

	return 0; //返回值:0,成功;其他:错误代码.
}

//读出时间
uint8_t RTC_Get(void){//读出当前时间值 //返回值:0,成功;其他:错误代码.
	static uint16_t daycnt=0;
	uint32_t timecount=0;
	uint32_t temp=0;
	uint16_t temp1=0;

	//【寄存器操作】因为HAL库的不完善,无法直接调用RTC_WriteTimeCounter函数。此处改用寄存器直接操作。
 	timecount=RTC->CNTH;//得到计数器中的值(秒钟数)
	timecount<<=16;
	timecount+=RTC->CNTL;
	//【寄存器操作】结束

	temp=timecount/86400;   //得到天数(秒钟数对应的)
	if(daycnt!=temp){//超过一天了
		daycnt=temp;
		temp1=1970;  //从1970年开始
		while(temp>=365){
		     if(Is_Leap_Year(temp1)){//是闰年
			     if(temp>=366)temp-=366;//闰年的秒钟数
			     else {temp1++;break;}
		     }
		     else temp-=365;       //平年
		     temp1++;
		}
		ryear=temp1;//得到年份
		temp1=0;
		while(temp>=28){//超过了一个月
			if(Is_Leap_Year(ryear)&&temp1==1){//当年是不是闰年/2月份
				if(temp>=29)temp-=29;//闰年的秒钟数
				else break;
			}else{
	            if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
	            else break;
			}
			temp1++;
		}
		rmon=temp1+1;//得到月份
		rday=temp+1;  //得到日期
	}
	temp=timecount%86400;     //得到秒钟数
	rhour=temp/3600;     //小时
	rmin=(temp%3600)/60; //分钟
	rsec=(temp%3600)%60; //秒钟
	rweek=RTC_Get_Week(ryear,rmon,rday);//获取星期
	return 0;
}

uint8_t RTC_Get_Week(uint16_t year,uint8_t month,uint8_t day){ //按年月日计算星期(只允许1901-2099年)//已由RTC_Get调用
	uint16_t temp2;
	uint8_t yearH,yearL;
	yearH=year/100;
	yearL=year%100;
	// 如果为21世纪,年份数加100
	if (yearH>19)yearL+=100;
	// 所过闰年数只算1900年之后的
	temp2=yearL+yearL/4;
	temp2=temp2%7;
	temp2=temp2+day+table_week[month-1];
	if (yearL%4==0&&month<3)temp2--;
	return(temp2%7); //返回星期值
}

六:温湿度传感器DHT11芯片驱动程序

设置

System Core——>GPIO——>点击PB2端口——>设置为GPIO输出(H O N H CHT11_DA)

dht11文件夹

dht11.h

#ifndef DHT11_DHT11_H_
#define DHT11_DHT11_H_

#include "stm32f1xx_hal.h"
#include "../delay/delay.h"

void DHT11_IO_OUT (void);
void DHT11_IO_IN (void);
void DHT11_RST (void);
uint8_t Dht11_Check(void);
uint8_t Dht11_ReadBit(void);
uint8_t Dht11_ReadByte(void);
uint8_t DHT11_Init (void);
uint8_t DHT11_ReadData(uint8_t *h);


#endif /* DHT11_DHT11_H_ */

dht11.c

#include "dht11.h"
#include "main.h"

void DHT11_IO_OUT (void){ //端口变为输出
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	GPIO_InitStruct.Pin = DHT11_DA_Pin;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}

void DHT11_IO_IN (void){ //端口变为输入
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	GPIO_InitStruct.Pin = DHT11_DA_Pin;
	GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
	GPIO_InitStruct.Pull = GPIO_PULLUP;
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}

void DHT11_RST (void){ //DHT11端口复位,发出起始信号(IO发送)
	DHT11_IO_OUT();
	HAL_GPIO_WritePin(GPIOB,DHT11_DA_Pin, GPIO_PIN_RESET);
	HAL_Delay(20); //拉低至少18ms
	HAL_GPIO_WritePin(GPIOB,DHT11_DA_Pin, GPIO_PIN_SET);
	delay_us(30); //主机拉高20~40us
}

uint8_t Dht11_Check(void){ //等待DHT11回应,返回1:未检测到DHT11,返回0:成功(IO接收)
	uint8_t retry=0;
    DHT11_IO_IN();//IO到输入状态
    while (HAL_GPIO_ReadPin(GPIOB,DHT11_DA_Pin)&&retry<100){//DHT11会拉低40~80us
        retry++;
        delay_us(1);
    }
    if(retry>=100)return 1; else retry=0;
    while (!HAL_GPIO_ReadPin(GPIOB,DHT11_DA_Pin)&&retry<100){//DHT11拉低后会再次拉高40~80us
        retry++;
        delay_us(1);
    }
    if(retry>=100)return 1;
    return 0;
}

uint8_t Dht11_ReadBit(void){ //从DHT11读取一个位 返回值:1/0
	uint8_t retry=0;
    while(HAL_GPIO_ReadPin(GPIOB,DHT11_DA_Pin)&&retry<100){//等待变为低电平
        retry++;
        delay_us(1);
    }
    retry=0;
    while(!HAL_GPIO_ReadPin(GPIOB,DHT11_DA_Pin)&&retry<100){//等待变高电平
        retry++;
        delay_us(1);
    }
    delay_us(40);//等待40us	//用于判断高低电平,即数据1或0
    if(HAL_GPIO_ReadPin(GPIOB,DHT11_DA_Pin))return 1; else return 0;
}

uint8_t Dht11_ReadByte(void){  //从DHT11读取一个字节  返回值:读到的数据
	uint8_t i,dat;
    dat=0;
    for (i=0;i<8;i++){
        dat<<=1;
        dat|=Dht11_ReadBit();
    }
    return dat;
}

uint8_t DHT11_Init (void){	//DHT11初始化
	DHT11_RST();//DHT11端口复位,发出起始信号
	return Dht11_Check(); //等待DHT11回应
}

uint8_t DHT11_ReadData(uint8_t *h){ //读取一次数据//湿度值(十进制,范围:20%~90%) ,温度值(十进制,范围:0~50°),返回值:0,正常;1,失败
	uint8_t buf[5];
	uint8_t i;
    DHT11_RST();//DHT11端口复位,发出起始信号
    if(Dht11_Check()==0){ //等待DHT11回应
        for(i=0;i<5;i++){//读取5位数据
            buf[i]=Dht11_ReadByte(); //读出数据
        }
        if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4]){	//数据校验
            *h=buf[0]; //将湿度值放入指针1
			h++;
            *h=buf[2]; //将温度值放入指针2
        }
    }else return 1;
    return 0;
}

main.c

#include "main.h"
#include "../../icode/led/led.h"
#include "../../icode/key/key.h"
#include "../../icode/delay/delay.h"
#include "../../icode/buzzer/buzzer.h"
#include "../../icode/relay/relay.h"
#include "../inc/retarget.h"//用于printf函数串口重映射
#include "../../icode/usart/usart.h"
#include "../../icode/adc/adc.h"
#include "../../icode/rtc/rtc.h"
#include "../../icode/dht11/dht11.h"


int main(void)
{
  uint8_t DHT11_BUF[2]={0};//用于存放DHT11数据

  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_CAN_Init();
  MX_SPI2_Init();
  MX_USART1_UART_Init();
  MX_USART2_UART_Init();
  MX_USART3_UART_Init();
  MX_USB_PCD_Init();

  RetargetInit(&huart1);//将printf()函数映射到UART1串口上
  HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1);//开启串口1接收中断
  HAL_Delay(500);//毫秒延时
  DHT11_Init();//传感器芯片初始化
  HAL_Delay(1500);//毫秒延时
  DHT11_ReadData(DHT11_BUF);//读出DHT11传感器数据(参数是存放数据的数组指针)


  while (1)
  {
      DHT11_ReadData(DHT11_BUF);//读出DHT11传感器数据(参数是存放数据的数组指针)
      printf("湿度:%02d% 温度:%02d℃\r\n",DHT11_BUF[0],DHT11_BUF[1]);//显示日期时间
      HAL_Delay(1500);//毫秒延时
  }
}

七:SPI总线闪存芯片驱动程序

设置

Connectivity——>SPI2y——>Mode选择全双工主机Full-Duplex Mastei
                    ——>设置SPI端口模式(PB15 SPI2_MOSI、PB14 SPI2_MOSI、PB13 SPI2_SCK)        
                    ——>设置参数Paramenter Setting
                        Motorola    帧模式:摩托罗拉
                        8Bits       数据大小:8位
                        MSB First   首位:MSB起始

                        2           分频器:2
                        18.0 MBits/s波特率:18.OMBHigh
                        High         高
                        2 Edge       CPHA:2边沿

                        Disabled     CRC校验:禁用
                        Software     使能信号:软件
                    ——>NVIC Setting——>勾选中断SPI12 global interrupt


System Core——>GPIO——>选中PB12——>设置PB12 W25Q128_cs为GPIO输出——>H O P H W25Q128_cs

w25q128文件 

w25qxx.h

#ifndef W25Q128_W25QXX_H_
#define W25Q128_W25QXX_H_

#include "stm32f1xx_hal.h" //HAL库文件声明
#include "../delay/delay.h"

//25系列FLASH芯片厂商与容量代号(厂商代号EF)
#define W25Q80    0XEF13
#define W25Q16    0XEF14
#define W25Q32    0XEF15
#define W25Q64    0XEF16
#define W25Q128   0XEF17
#define W25Q256 0XEF18
#define EX_FLASH_ADD 0x000000 //W25Q128的地址是24位宽
extern uint16_t W25QXX_TYPE;//定义W25QXX芯片型号
extern SPI_HandleTypeDef hspi2;
//
//指令表
#define W25X_WriteEnable             0x06
#define W25X_WriteDisable            0x04
#define W25X_ReadStatusReg1      0x05
#define W25X_ReadStatusReg2      0x35
#define W25X_ReadStatusReg3      0x15
#define W25X_WriteStatusReg1         0x01
#define W25X_WriteStatusReg2         0x31
#define W25X_WriteStatusReg3     0x11
#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 W25X_Enable4ByteAddr         0xB7
#define W25X_Exit4ByteAddr        0xE9
uint8_t SPI2_ReadWriteByte(uint8_t  TxData);//SPI2总线底层读写
void W25QXX_CS(uint8_t a);//W25QXX片选引脚控制
uint8_t W25QXX_Init(void);//初始化W25QXX函数
uint16_t  W25QXX_ReadID(void);//读取FLASH ID
uint8_t W25QXX_ReadSR(uint8_t regno);//读取状态寄存器
void W25QXX_4ByteAddr_Enable(void);//使能4字节地址模式
void W25QXX_Write_SR(uint8_t regno,uint8_t  sr);//写状态寄存器
void W25QXX_Write_Enable(void);//写使能
void W25QXX_Write_Disable(void);//写保护
void W25QXX_Write_NoCheck(uint8_t*  pBuffer,uint32_t WriteAddr,uint16_t  NumByteToWrite);//无检验写SPI FLASH
void W25QXX_Read(uint8_t* pBuffer,uint32_t  ReadAddr,uint16_t NumByteToRead);//读取flash
void W25QXX_Write(uint8_t* pBuffer,uint32_t  WriteAddr,uint16_t NumByteToWrite);//写入flash
void W25QXX_Erase_Chip(void);//整片擦除
void W25QXX_Erase_Sector(uint32_t  Dst_Addr);//扇区擦除
void W25QXX_Wait_Busy(void);//等待空闲
void W25QXX_PowerDown(void);//进入掉电模式
void W25QXX_WAKEUP(void);//唤醒

#endif /* W25Q128_W25QXX_H_ */

w25qxx.c

里面定义了很多函数 

#include "w25qxx.h"
#include "main.h"
uint16_t W25QXX_TYPE=W25Q128;//默认是W25Q128

//4Kbytes为一个Sector
//16个扇区为1个Block
//W25Q128
//容量为16M字节,共有128个Block,4096个Sector
//SPI2总线读写一个字节
//参数是写入的字节,返回值是读出的字节

uint8_t SPI2_ReadWriteByte(uint8_t TxData)
{
    uint8_t Rxdata;//定义一个变量Rxdata
     HAL_SPI_TransmitReceive(&hspi2,&TxData,&Rxdata,1,1000);//调用固件库函数收发数据
    return Rxdata;//返回收到的数据
}

void W25QXX_CS(uint8_t a)//软件控制函数(0为低电平,其他值为高电平)
{
    if(a==0)HAL_GPIO_WritePin(W25Q128_CS_GPIO_Port, W25Q128_CS_Pin, GPIO_PIN_RESET);
    else  HAL_GPIO_WritePin(W25Q128_CS_GPIO_Port,  W25Q128_CS_Pin, GPIO_PIN_SET);
}
//初始化SPI FLASH的IO口
uint8_t W25QXX_Init(void)
{
    uint8_t temp;//定义一个变量temp
    W25QXX_CS(1);//0片选开启,1片选关闭
    W25QXX_TYPE = W25QXX_ReadID();//读取FLASH  ID.
    if(W25QXX_TYPE == W25Q256)//SPI FLASH为W25Q256时才用设置为4字节地址模式
    {
       temp = W25QXX_ReadSR(3);//读取状态寄存器3,判断地址模式
       if((temp&0x01)==0)//如果不是4字节地址模式,则进入4字节地址模式
       {
           W25QXX_CS(0);//0片选开启,1片选关闭
           SPI2_ReadWriteByte(W25X_Enable4ByteAddr);//发送进入4字节地址模式指令
           W25QXX_CS(1);//0片选开启,1片选关闭
       }
    }
    if(W25QXX_TYPE==W25Q256||W25QXX_TYPE==W25Q128||W25QXX_TYPE==W25Q64
    ||W25QXX_TYPE==W25Q32||W25QXX_TYPE==W25Q16||W25QXX_TYPE==W25Q80)
    return 0; else return 1;//如果读出ID是现有型号列表中的一个,则识别芯片成功!
}

main.c

#include "main.h"
#include "../../icode/led/led.h"
#include "../../icode/key/key.h"
#include "../../icode/delay/delay.h"
#include "../../icode/buzzer/buzzer.h"
#include "../../icode/relay/relay.h"
#include "../inc/retarget.h"//用于printf函数串口重映射
#include "../../icode/usart/usart.h"
#include "../../icode/adc/adc.h"
#include "../../icode/rtc/rtc.h"
#include "../../icode/dht11/dht11.h"
#include "../../icode/w25q128/w25qxx.h"



int main(void)
{

  uint8_t EX_FLASH_BUF[1];//W25Q128芯片数据缓存数组

  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_CAN_Init();
  MX_SPI2_Init();
  MX_USART1_UART_Init();
  MX_USART2_UART_Init();
  MX_USART3_UART_Init();
  MX_USB_PCD_Init();

  RetargetInit(&huart1);//将printf()函数映射到UART1串口上
  HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1);//开启串口1接收中断
  HAL_Delay(500);//毫秒延时
  W25QXX_Init();//W25QXX初始化
  printf("W25Q128测试程序:按KEY1键显示芯片ID,按KEY2键将0x00地址中的数值加1 \n\r");//显示程序说明文字
 


while (1)
  {
	  if(KEY_1()){
		  EX_FLASH_BUF[0]=W25QXX_ReadID();//读取W25QXX芯片的ID码。W25Q128芯片十进制ID是61207(十六进制表示是0xEF17)
		  printf("芯片ID:%x \n\r",EX_FLASH_BUF[0]);//显示芯片ID
		  BUZZER_SOLO1();//提示音
	  }
	  if(KEY_2()){
		BUZZER_SOLO1();//提示音
		W25QXX_Read(EX_FLASH_BUF,EX_FLASH_ADD,1);//读出W25QXX芯片数据(参数:读出数据存放的数组,读取的开始地址,数量)
		EX_FLASH_BUF[0]++;//数据加1
		if(EX_FLASH_BUF[0]>200)EX_FLASH_BUF[0]=0;//如果数值大于指定最大值则清0
		W25QXX_Write(EX_FLASH_BUF,EX_FLASH_ADD,1);//写入W25QXX芯片数据(参数:读出数据存放的数组,读取的开始地址,数量)
		printf("读出0x00地址数据:%d \n\r",EX_FLASH_BUF[0]);//读出数据
		BUZZER_SOLO1();//提示音
	  }
  }

} 

八:USB从设备串口驱动程序

设置

时钟树视图——>To USB使USB最终频率为48MHz


Connectivity——>USB——>勾选Device设备——>设置端口为USB接口PA12 USB_DP;PA12 USB_DP——>点击参数设置Parameter Setting——>参数按默认设置
                   ——>MVIC Setting——>勾选USB低优先级USB low priority or CAN RX0 interrupts


Middleware——>USB_DEVICE——>选中串口Class For Fs IP Communication Device Class (Virtual Port Com)
                          Disable禁用
                          Audio Device Class音频设备类
                          Communication Device Class (Virtual Port Com)通信设备类(虚拟串口)
                          Download Firmware Update Class (DFu)、下载固件更新类(DFU)
                          Human lnterface Device Class (HID)人机界面设备类(HID)
                          Custom Human Interface Device Class(HID)自定义人机界面设备类(HID)
                          Mass Storage Class 大容量存储类


Project Manager工程管理选项卡——>Project——>Linker Settings
                                            Minimum Heap Size  0x1000
                                            Minimum Stack Size 0x1000

...\USB_DEVICE\App

usbd_cdc_if.h

#define USB_REC_LEN   200//定义USB串口最大接收字节数
extern uint8_t USB_RX_BUF[USB_REC_LEN];//接收缓冲,最大USB_REC_LEN个字节.末字节为换行符
extern uint16_t USB_RX_STA;//接收状态标记(接收到的有效字节数量)



void USB_printf(const char *format,  ...);//USB模拟串口的打印函数

usbd_cdc_if.c

uint8_t USB_RX_BUF[USB_REC_LEN];//接收缓冲,最大USB_REC_LEN个字节.
uint16_t USB_RX_STA=0;//接收状态标记(接收到的有效字节数量)


static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
  /* USER CODE BEGIN 6 */
    if(*LenTxState != 0) return  USBD_BUSY;
  while(hcdc->TxState)
  {
     if(HAL_GetTick()-TimeStart > 10)
  return USBD_BUSY;
     else
  break;
  }
  USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf,  Len);
  result =  USBD_CDC_TransmitPacket(&hUsbDeviceFS);
  TimeStart = HAL_GetTick();
  while(hcdc->TxState)
    {
       if(HAL_GetTick()-TimeStart > 10)
         return USBD_BUSY;
    }
  /* USER CODE END 7 */
  return result;
}



#include 
void USB_printf(const char *format, ...)//USB模拟串口的打印函数
{
    va_list args;
    uint32_t length;
    va_start(args, format);
    length = vsnprintf((char  *)UserTxBufferFS, APP_TX_DATA_SIZE, (char  *)format, args);
    va_end(args);
    CDC_Transmit_FS(UserTxBufferFS, length);
}

main.c

#include "../../icode/led/led.h"
#include "../../icode/key/key.h"
#include "../../icode/delay/delay.h"
#include "../../icode/buzzer/buzzer.h"
#include "../../icode/relay/relay.h"
#include "../inc/retarget.h"//用于printf函数串口重映射
#include "../../icode/usart/usart.h"
#include "../../icode/adc/adc.h"
#include "../../icode/rtc/rtc.h"
#include "../../icode/dht11/dht11.h"
#include "../../icode/w25q128/w25qxx.h"
#include "../../USB_DEVICE/App/usbd_cdc_if.h"



int main(void)
{


  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_CAN_Init();
  MX_SPI2_Init();
  MX_USART1_UART_Init();
  MX_USART2_UART_Init();
  MX_USART3_UART_Init();
  MX_USB_DEVICE_Init();

  RetargetInit(&huart1);//将printf()函数映射到UART1串口上
  HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1);//开启串口1接收中断
  HAL_CAN_MspDeInit(&hcan);//关闭CAN功能,使USB功能可被电脑识别(因USB与CAN共用一个RAM空间,不能同时使用)

 while (1)
 {
	//USB模拟串口的查寻接收处理(其编程原理与USART1串口收发相同)
	if(USB_RX_STA!=0)//判断是否有数据
	{
		USB_printf("USB_RX:");//向USB模拟串口发送字符串
		CDC_Transmit_FS(USB_RX_BUF,USB_RX_STA);//USB串口发送:将接收的数据发回给电脑端(参数1是数据内容,参数2是数据量)
		USB_printf("\r\n");//向USB模拟串口发送字符串(回车)
		USB_RX_STA=0;//数据标志位清0
		memset(USB_RX_BUF,0,sizeof(USB_RX_BUF));//USB串口数据寄存器清0
	}
 }

}

九:省电模式、CRC与芯片ID

1.省电模式(睡眠、停机、待机) 

STM32基于CubeIDE和HAL库 基础入门学习笔记:功能驱动与应用_第1张图片

1.1 睡眠模式测试程序

main.c

 while (1)
  {
	  LED_1(1);//LED1灯控制(1点亮,0熄灭)
	  LED_2(0);//LED2灯控制(1点亮,0熄灭)
	  HAL_Delay(100);//在主循环里写入HAL库的毫秒级延时函数
	  LED_1(0);//LED1灯控制(1点亮,0熄灭)
	  LED_2(1);//LED2灯控制(1点亮,0熄灭)
	  HAL_Delay(100);//在主循环里写入HAL库的毫秒级延时函数

     //睡眠模式的启动函数
	  HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON,PWR_SLEEPENTRY_WFI);//进入睡眠模式(任意中断可唤醒)

}

//参数1:PWR_MAINREGULATOR_ON主电源开启    PWR_LOWPOWERREGULATOR_ON 低功耗电源开启
//参数2:PWR_SLEEPENTRY_WFI中断唤醒        PWR_SLEEPENTRY_WFE 事件唤醒

1.2 停机模式测试程序

设置一个外部中断唤醒的端口 

PAO端口——>设置为外部中断GPIO_EXT10    


System Core——>GPIO——>右边点击GPIO栏——>选中PA0        
                                            ——>GPIO mode                 External lnterrupt Mode with Falling edge trigger detection下降沿触发
                                            ——>GPIO Pull-up/Pull-down    Pull-up上拉
                                            ——>User Label                KEY1
                 ——>右边点击MVIC栏——>勾选允许中断EXTl line0 interrupt

main.c

  while (1)
  {
	  LED_1(1);//LED1灯控制(1点亮,0熄灭)
	  LED_2(0);//LED2灯控制(1点亮,0熄灭)
	  HAL_Delay(100);//在主循环里写入HAL库的毫秒级延时函数
	  LED_1(0);//LED1灯控制(1点亮,0熄灭)
	  LED_2(1);//LED2灯控制(1点亮,0熄灭)
	  HAL_Delay(100);//在主循环里写入HAL库的毫秒级延时函数
	  if(KEY_2()) //按键KEY2判断为1时按键按下
	  {
		printf("进入【停机】状态!(按KEY1键外部中断唤醒) \n\r");//串口发送
		BUZZER_SOLO1();//蜂鸣器输出单音的报警音(样式1:HAL库的精准延时函数)

        //停机模式的启动函数
		HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);//进入【停机】模式
		//此处进入停机状态!!!
		//接下来的程序在唤醒后执行
		SystemClock_Config();//唤醒后重新初始化时钟
		printf("退出【停机】状态! \n\r");//串口发送
	  }

  }

1.3 待机模式测试程序

设置

待机模武是在停机模式的基础上美闭了SRAM的电源
使正在运行的程扇全部丢失只能复位重启


由于在单片机正常运行PA0端口还需要被作为按键被使用:只有讲入德机状态之前才需要设置为WKUP功能

    设置PAO——>SYS_WKUP(此端口为专用待机唤醒端口)


如何解决呢?还是将PAO被定义为GPIO输入模式:WKUP功能采用程序代码来设置
    System Core——>SYS——>System Wake-Up导致不能被勾选

main.c

RetargetInit(&huart1);//将printf()函数映射到UART1串口上
  HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1);//开启串口1接收中断
  printf("\n\r单片机启动!(按KEY2按键输入【待机】模式)\n\r");//串口发送
  if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB) != RESET)//判断本次复位是不是从待机中唤醒
  {
	//可在此插入待机唤醒的处理程序
	printf("从【待机】模式中唤醒!\n\r");//串口发送
	HAL_PWR_DisableWakeUpPin(PWR_WAKEUP_PIN1);//禁止WKUP引脚的唤醒功能
	__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);//清除唤醒标志位
  }



  while (1)
  {
	  LED_1(1);//LED1灯控制(1点亮,0熄灭)
	  LED_2(0);//LED2灯控制(1点亮,0熄灭)
	  HAL_Delay(100);//在主循环里写入HAL库的毫秒级延时函数
	  LED_1(0);//LED1灯控制(1点亮,0熄灭)
	  LED_2(1);//LED2灯控制(1点亮,0熄灭)
	  HAL_Delay(100);//在主循环里写入HAL库的毫秒级延时函数
	  if(KEY_2()) //按键KEY2判断为1时按键按下
	  {
		printf("进入【待机】状态!(按IoT开发板上的“休眠唤醒”键唤醒) \n\r");//串口发送
		BUZZER_SOLO1();//蜂鸣器输出单音的报警音(样式1:HAL库的精准延时函数)
		HAL_GPIO_WritePin(GPIOA,KEY1_Pin, GPIO_PIN_RESET);//PB0端口变低电平,准备好唤醒键初始电平
		__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);//清除 WKUP唤醒键 状态位
		HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);//使能WKUP引脚的唤醒功能(使能PA0)
		HAL_PWR_EnterSTANDBYMode();//进入【待机模式】
	  }

 }

2.CRC数据校验方式与芯片ID测试程序

CRC:本质是一个32位的带多项式计算的寄存器
      多用于数据通讯过程中的校验

设置

Computing——>CRC——>勾选激活Activated

main.c

#include "main.h"
#include "../../icode/led/led.h"
#include "../../icode/key/key.h"
#include "../../icode/delay/delay.h"
#include "../../icode/buzzer/buzzer.h"
#include "../../icode/relay/relay.h"
#include "../inc/retarget.h"//用于printf函数串口重映射
#include "../../icode/usart/usart.h"
#include "../../icode/adc/adc.h"
#include "../../icode/rtc/rtc.h"
#include "../../icode/dht11/dht11.h"
#include "../../icode/w25q128/w25qxx.h"



static const uint32_t CRCBUF[4] = {0x61,0x62,0x63,0x64};

int main(void)
{

  uint32_t a,b,c;

  MX_GPIO_Init();
  MX_ADC1_Init();
  MX_CAN_Init();
  MX_SPI2_Init();
  MX_USART1_UART_Init();
  MX_USART2_UART_Init();
  MX_USART3_UART_Init();
  MX_CRC_Init();
 
  RetargetInit(&huart1);//将printf()函数映射到UART1串口上
  HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1);//开启串口1接收中断
  printf("\n\rCRC计算测试程序\n\r");//串口发送


   while (1)
  {
      //数据校验
	  c = HAL_CRC_Calculate(&hcrc,(uint32_t *)CRCBUF,4);//载入CRC数据并返回计算结果(参数2:要计算的数组,参数3:数量)开始前清除DR
//	  c = HAL_CRC_Accumulate(&hcrc,(uint32_t *)CRCBUF,4);//载入CRC数据并返回计算结果(参数2:要计算的数组,参数3:数量)保留了上一次的内容,适用于不连续的累加式校验
	  printf("CRC计算结果:%08X \n\r",c);//将CRC计算结果显示在超级终端


      //读取芯片ID
	  a = *(__IO uint32_t *)(0X1FFFF7E8); //读出3个32位芯片ID(高字节)
	  b = *(__IO uint32_t *)(0X1FFFF7EC); //
	  c = *(__IO uint32_t *)(0X1FFFF7F0); //(低字节)
	  printf("芯片ID: %08X %08X %08X \r\n",a,b,c); //从串口输出16进制ID

	  while (1);//执行结束后在此循环
  }

{

十:外部中断与定时器

1.外部中断(外部中断的按键测试程序)

STM32基于CubeIDE和HAL库 基础入门学习笔记:功能驱动与应用_第2张图片

 STM32基于CubeIDE和HAL库 基础入门学习笔记:功能驱动与应用_第3张图片

 设置

点击PA0——>设置为外部中断GPIO_EX10


System Core——>GPIO——>点击右侧GPIO选项卡——>点击PA0一行——>设置为 下降沿Extermal Interrupt Mode、上拉Pull-up、用户标注KEY1 
                  ——>点击右侧MVIC选项卡——>勾选EXTIO中断允许
           ——>NVIC——>点击NVIC选项卡——>EXTI16: PVD中断、EXTI18:USB中断、EXTI17: RTC闹钟中断
                  ——>点击Code generation选项卡——>EXTl line0 interrupt设置生成代码

stm32f1xx_it.c

void EXTI0_IRQHandler(void)    //stm32f1xx_hal_gpio.c里面可以查看
{
  HAL_GPIO_EXTI_IRQHandler(KEY1_Pin);
}

main.c

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){//外部中断回调函数
	if(GPIO_Pin == KEY1_Pin){//判断产生中断的端口
		if(KEY_1()){//再通过按键处理程序判断按键按下和放开
			LED_1_Contrary();//每按一次按键,LED状态反转一次
		}
    }
}




  while (1)
  {

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

key.c

#include "key.h"

uint8_t KEY_1(void)
{
	uint8_t a;
	a=0;//如果未进入按键处理,则返回0
	if(HAL_GPIO_ReadPin(GPIOA,KEY1_Pin)==GPIO_PIN_RESET){//读按键接口的电平
//		HAL_Delay(20);//延时去抖动(外部中断回调函数调用时不能使用系统自带的延时函数)
		delay_us(20000);//延时去抖动
		if(HAL_GPIO_ReadPin(GPIOA,KEY1_Pin)==GPIO_PIN_RESET){ //读按键接口的电平
			a=1;//进入按键处理,返回1
		}
	}
	while(HAL_GPIO_ReadPin(GPIOA,KEY1_Pin)==GPIO_PIN_RESET); //等待按键松开
	delay_us(20000);//延时去抖动(避开按键放开时的抖动)
	return a;
}

uint8_t KEY_2(void)
{
	uint8_t a;
	a=0;//如果未进入按键处理,则返回0
	if(HAL_GPIO_ReadPin(GPIOA,KEY2_Pin)==GPIO_PIN_RESET){//读按键接口的电平
//		HAL_Delay(20);//延时去抖动(外部中断回调函数调用时不能使用系统自带的延时函数)
		delay_us(20000);//延时去抖动
		if(HAL_GPIO_ReadPin(GPIOA,KEY2_Pin)==GPIO_PIN_RESET){ //读按键接口的电平
			a=1;//进入按键处理,返回1
		}
	}
	while(HAL_GPIO_ReadPin(GPIOA,KEY2_Pin)==GPIO_PIN_RESET); //等待按键松开
	delay_us(20000);//延时去抖动(避开按键放开时的抖动)
	return a;
}

2.定时器

Tout单位微秒 =(ARR+1) × (PCS+1) / Tclk

定时时间(uS)=(计数周期+1)×(分频系数+1)÷输入时钟频率(MHz)

2.1 定时器中断的闪灯测试程序

设置

Timers——>TIM2——>Clock Source设置为内部时钟源Intemal Clock
             ——>点击参数设置Paramater Settings
                    Prescaler(PSC - 16 bits vallue)        9999分频系数
                    Counter Mode                           up计数模式
                    Counter Period (AutoReload Register    7199计数周期
                    lnternall Clock Division (CKD)         No Dinvision时钟分频因子
                    auto-reload preload                    Enable自动重载初值


NVIC Setting——>勾选TIM2 global interrupt

main.c

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){//定时器中断回调函数
	if(htim==(&htim2))//判断产生中断的定时器
	{
		LED_2_Contrary();//LED状态反转
	}
}



MX_TIM2_Init();

HAL_TIM_Base_Start_IT(&htim2);//开启定时器中断(必须开启才能进入中断处理回调函数)



while (1)
  {

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

2.2 定时器中断的PWM调光测试程序

STM32基于CubeIDE和HAL库 基础入门学习笔记:功能驱动与应用_第4张图片

 设置

PB0——>设置为TM3_CH3


Timers——>TIM3——>勾选内部时钟Internal Clock——>Channel3设置为PWM Generation CH3


Parameter——>
         Counter Settings
            Prescaler (PSC - 16 bits value)                            71             分频系数
            Counter Mode                                               Up             计数模式
            Counter Period (AutoReload Register - 16 bits value )      499            计数周期
            lnternal Clock Division (CKD)                              No Division    内部时钟因子
            auto-reload preload                                        Enable         自动重装初值
        PWM Generation Channal3
            Mode                       PWM mode 1    PWN模式
            Pulse (16 bits value)      0             脉冲16位
            Output compare preload     Enable        输出占空比 
            Fast Mode                  Disable       快速模式
            CH Polarity                High          通道极性
    

NMIC Setting——>勾选TIM3 global interrupt允许中断


GPIO Setting——>选中PB0——>Altemate Function Push Pull、High、LED1

main.c

int main(void)
{
  uint16_t a=0;

  MX_CRC_Init();
  MX_TIM2_Init();
  MX_TIM3_Init();
  
  RetargetInit(&huart1);//将printf()函数映射到UART1串口上
  HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1);//开启串口1接收中断
  HAL_TIM_Base_Start_IT(&htim2);//开启定时器中断(必须开启才能进入中断处理回调函数)
  HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_3);//开启定时器PWM输出


   while (1)
  {
	  __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_3,a);//设置占空比函数(参数3是PWM比值,范围0~ARR计数周期)
	  a++;//占空比值加1
	  if(a>=499)a=0;//当占空比值达到最大后清0
	  HAL_Delay(10);//在主循环每10毫秒循环一次
  }
}

十一:RS485总线有线通讯驱动程序

设置

将PA2设置为——>USAR_T2端口
将PA3设置为——>SAR_RX端口


Connecticity——>USART2——>Mode设置为异步模式Asynchronous
                     ——>Parameter Settings参数设置
                            Baud Rate        波特率        115200 Bits/s
                            Word Length      位宽度        8 Bits (including Parity)
                            Parity           校验          None
                            Stop Bits        停止位        1


                            Data Direction    数据方向    Receive and Transmit
                            Over Sampling     采样        16 Samples
                     ——>NMIC Settings——>勾选运行中断USART2 global interrupt


System Core——>GPIO——>点击右侧的GPIO选项卡——>点击PA8这一行——>L O N H RS485_RE

rs485文件夹

rs485.h

#ifndef RS485_RS485_H_
#define RS485_RS485_H_

#include "stm32f1xx_hal.h" //HAL库文件声明
#include //用于字符串处理的库
#include 
#include 
#include "stdio.h"

extern UART_HandleTypeDef huart2;//声明USART2的HAL库结构体
void RS485_printf (char *fmt, ...);  //RS485发送

#endif /* RS485_RS485_H_ */

rs485.c

#include "rs485.h"
#include "../usart/usart.h"
#include "main.h"

/*
RS485总线通信,使用UART8,这是RS485专用的printf函数
调用方法:RS485_printf("123"); //向UART8发送字符123
*/

void RS485_printf (char *fmt, ...)
{
    char buff[USART2_REC_LEN+1];  //用于存放转换后的数据 [长度]
    uint16_t i=0;
    va_list arg_ptr;
    HAL_GPIO_WritePin(RS485_RE_GPIO_Port,RS485_RE_Pin, GPIO_PIN_SET);//RS485收发选择线RE为高电平(发送)
    va_start(arg_ptr,fmt);
    vsnprintf(buff, USART2_REC_LEN+1,fmt,arg_ptr);//数据转换
    i=strlen(buff);//得出数据长度
    if(strlen(buff)>USART2_REC_LEN)i=USART2_REC_LEN;//如果长度大于最大值,则长度等于最大值(多出部分忽略)
    HAL_UART_Transmit(&huart2,(uint8_t *)buff,i,0xffff);//串口发送函数(串口号,内容,数量,溢出时间)
    va_end(arg_ptr);
    HAL_GPIO_WritePin(RS485_RE_GPIO_Port,RS485_RE_Pin, GPIO_PIN_RESET);//RS485收发选择线RE为低电平(接收)
}
//所有USART串口的中断回调函数HAL_UART_RxCpltCallback,统一存放在【USART1.C】文件中。

usart.c

#include "usart.h"

uint8_t USART1_RX_BUF[USART1_REC_LEN];//接收缓冲,最大USART_REC_LEN个字节.
uint16_t USART1_RX_STA=0;//接收状态标记//bit15:接收完成标志,bit14:接收到0x0d,bit13~0:接收到的有效字节数目
uint8_t USART1_NewData;//当前串口中断接收的1个字节数据的缓存

uint8_t USART2_RX_BUF[USART2_REC_LEN];//接收缓冲,最大USART_REC_LEN个字节.
uint16_t USART2_RX_STA=0;//接收状态标记//bit15:接收完成标志,bit14:接收到0x0d,bit13~0:接收到的有效字节数目
uint8_t USART2_NewData;//当前串口中断接收的1个字节数据的缓存
uint8_t RS485orBT;//当RS485orBT标志位为1时是RS485模式,为0时是蓝牙模式

uint8_t USART3_RX_BUF[USART3_REC_LEN];//接收缓冲,最大USART_REC_LEN个字节.
uint16_t USART3_RX_STA=0;//接收状态标记//bit15:接收完成标志,bit14:接收到0x0d,bit13~0:接收到的有效字节数目
uint8_t USART3_NewData;//当前串口中断接收的1个字节数据的缓存

void  HAL_UART_RxCpltCallback(UART_HandleTypeDef  *huart)//串口中断回调函数
{
	if(huart ==&huart1)//判断中断来源(串口1:USB转串口)
    {
       printf("%c",USART1_NewData); //把收到的数据以 a符号变量 发送回电脑
       if((USART1_RX_STA&0x8000)==0){//接收未完成
           if(USART1_RX_STA&0x4000){//接收到了0x0d
               if(USART1_NewData!=0x0a)USART1_RX_STA=0;//接收错误,重新开始
               else USART1_RX_STA|=0x8000;   //接收完成了
           }else{ //还没收到0X0D
               if(USART1_NewData==0x0d)USART1_RX_STA|=0x4000;
               else{
                  USART1_RX_BUF[USART1_RX_STA&0X3FFF]=USART1_NewData; //将收到的数据放入数组
                  USART1_RX_STA++;  //数据长度计数加1
                  if(USART1_RX_STA>(USART1_REC_LEN-1))USART1_RX_STA=0;//接收数据错误,重新开始接收
               }
           }
       }
       HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1); //再开启接收中断
    }


    if(huart ==&huart2)//判断中断来源(RS485)
    {
	   USART2_RX_BUF[0]=USART2_NewData;//收到数据放入缓存数组(只用到1个数据存放在数组[0])
	   USART2_RX_STA++;//数据接收标志位加1
       HAL_UART_Receive_IT(&huart2,(uint8_t *)&USART2_NewData, 1); //再开启接收中断
    }
	if(huart ==&huart3)//判断中断来源(串口3:WIFI模块)
	{
		printf("%c",USART3_NewData); //把收到的数据以 a符号变量 发送回电脑
		HAL_UART_Receive_IT(&huart3,(uint8_t *)&USART3_NewData,1); //再开启接收中断
	}
}

main.c

#include "../../icode/rs485/rs485.h"


int main(void)
{
HAL_UART_Receive_IT(&huart2,(uint8_t *)&USART2_NewData,1); //开启串口2接收中断



while (1)
  {
      if(USART2_RX_STA!=0)//串口2判断中断接收标志位【处理从RS485外部设备接收的字符】
      {
    	  BUZZER_SOLO1();//蜂鸣器输出单音的报警音(样式1:HAL库的精准延时函数)
    	  RS485_printf("%c",USART2_RX_BUF[0]); //串口发送
    	  USART2_RX_STA=0;//清除标志位
      }
	  if(KEY_1())//按下KEY1判断
	  {
		  BUZZER_SOLO2();//提示音
		  RS485_printf("A");//向RS485发送字符A
	  }
	  if(KEY_2())//按下KEY2判断
	  {
		  BUZZER_SOLO2();//提示音
		  RS485_printf("B");//向RS485发送字符B
	  }

  }

}

十二:CAN总线有线通讯驱动程序

设置

配置时钟树视图——>APB1设置为36(CAN功能由APB1提供时钟源)


Connecttivity——>CAN——>勾选激活Activated
             ——>PB8和PB9设置为CAN接口
             ——>点击参数设置Parameter Setting
                    Bit Timings Parameters
                        Prescaler (for Time Quantum)            9
                        Time Quantum                            250.0ns
                        Time Quanta in Bit Segment 1            8 Times
                        Time Quanta in Bit Segment 2            7 Times
                        Time for one Bit                        4000 ns
                        Baud Rate                               250000 bi/s
                        ReSynchronization Jump Width            1 Time
                    Basic Parameters
                        Time Triggered Communication Mode       Disable
                        Automatic Bus-Off Management            Disable
                        Automatic Wake-Up Mode                  Disable
                        Automatic Retransmission                Disable
                        Receive Fifo Locked Mode                Disable
                        Transmit Fifo Priority                  Disable
                    Advanced Parameters
                        Operating Mode                          Normal
              ——>MVIC Settings——>勾选CAN RX1

can文件夹

can1.h

#ifndef CAN_CAN1_H_
#define CAN_CAN1_H_

#include "stm32f1xx_hal.h" //HAL库文件声明
#include //用于字符串处理的库
#include 
#include 
#include "stdio.h"

extern CAN_HandleTypeDef hcan;//声明的HAL库结构体

CAN_TxHeaderTypeDef     TxMeg;//CAN发送设置相关结构体
CAN_RxHeaderTypeDef     RxMeg;//CAN接收设置相关结构体

#define CAN1_ID_H      0x0000 //32位基础ID设置(高16位)
#define CAN1_ID_L      0x0000 //32位基础ID设置(低16位)
#define CAN1_MASK_H    0x0000 //32位屏蔽MASK设置(高16位)
#define CAN1_MASK_L    0x0000 //32位屏蔽MASK设置(低16位)
#define CAN1_REC_LEN  200//定义CAN1最大接收字节数

extern uint8_t  CAN1_RX_BUF[CAN1_REC_LEN];//接收缓冲,末字节为换行符
extern uint16_t CAN1_RX_STA;//接收状态标记

void CAN_User_Init(CAN_HandleTypeDef* hcan  );//CAN用户初始化函数
void  HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan);//CAN接收回调函数
uint8_t  CAN1_SendNormalData(CAN_HandleTypeDef*  hcan,uint16_t ID,uint8_t *pData,uint16_t  Len);//CAN发送函数
void CAN1_printf (char *fmt, ...);//CAN总线通信,使用CAN1,这是CAN专用的printf函数

#endif /* CAN_CAN1_H_ */

can1.c

#include "can1.h" //库文件声明
#include "main.h"

CAN_HandleTypeDef hcan;//声明的HAL库结构体

uint8_t CAN1_RX_BUF[CAN1_REC_LEN];//接收缓冲,最大CAN1_REC_LEN个字节.末字节为换行符
uint16_t CAN1_RX_STA;//接收状态标记

void CAN_User_Init(CAN_HandleTypeDef* hcan  )//CAN总线用户初始化函数
{
    CAN_FilterTypeDef  sFilterConfig;
    HAL_StatusTypeDef  HAL_Status;
    TxMeg.IDE = CAN_ID_STD;//扩展帧标识(STD标准帧/EXT扩展帧)
    TxMeg.RTR = CAN_RTR_DATA;//远程帧标识(DATA数据帧/REMOTE远程帧)
    sFilterConfig.FilterBank = 0;//过滤器0
    sFilterConfig.FilterMode =   CAN_FILTERMODE_IDMASK;//设为IDLIST列表模式/IDMASK屏蔽模式
    sFilterConfig.FilterScale =  CAN_FILTERSCALE_32BIT;//过滤器位宽度
    sFilterConfig.FilterIdHigh = CAN1_ID_H;//32位基础ID设置(高16位)
    sFilterConfig.FilterIdLow  = CAN1_ID_L;//32位基础ID设置(低16位)
    sFilterConfig.FilterMaskIdHigh =  CAN1_MASK_H;//32位屏蔽MASK设置(高16位)
    sFilterConfig.FilterMaskIdLow  =  CAN1_MASK_L;//32位屏蔽MASK设置(低16位)
    sFilterConfig.FilterFIFOAssignment =  CAN_RX_FIFO1;//接收到的报文放入FIFO1位置
    sFilterConfig.FilterActivation =  ENABLE;//ENABLE激活过滤器,DISABLE禁止过滤器
    sFilterConfig.SlaveStartFilterBank  =  0;//过滤器组设置(单个CAN总线时无用)
    HAL_Status=HAL_CAN_ConfigFilter(hcan,&sFilterConfig);//将以上结构体参数设置到CAN寄存器中
    if(HAL_Status!=HAL_OK){//判断开启是否成功
       //开启CAN总线失败的处理程序,写在此处
    	printf("\n\rCAN设置失败!\n\r"); //串口发送
    }
    HAL_Status=HAL_CAN_Start(hcan);  //开启CAN总线功能
    if(HAL_Status!=HAL_OK){//判断开启是否成功
       //开启CAN总线失败的处理程序,写在此处
    	printf("\n\rCAN初始化失败!\n\r"); //串口发送
    }
    //若不使用CAN中断,可删除以下4行
    HAL_Status=HAL_CAN_ActivateNotification(hcan,CAN_IT_RX_FIFO1_MSG_PENDING);//开启CAN总线中断
    if(HAL_Status!=HAL_OK){
       //开启CAN总线挂起中断失败的处理程序,写在此处
    	printf("\n\rCAN中断初始化失败!\n\r"); //串口发送
    }
}
void  HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan)  //接收回调函数(函数名不可改)
{
    uint8_t  Data[8];//接收缓存数组
    HAL_StatusTypeDef HAL_RetVal;//判断状态的枚举
	HAL_RetVal=HAL_CAN_GetRxMessage(hcan,CAN_RX_FIFO1,&RxMeg,Data);//接收邮箱中的数据
	if (HAL_OK==HAL_RetVal){//判断接收是否成功
		//接收成功后的数据处理程序,写在此处。(数据在Data数组中)
		//以下2行是采用简单的寄存器查寻方式处理接收数据,每次只接收1位。在实际项目中的复杂接收程序可自行编写。
		CAN1_RX_BUF[0]=Data[0];//将接收到的数据放入缓存数组(因只用到1个数据,所以只存放在数据[0]位置)
		CAN1_RX_STA++;//数据接收标志位加1
	}
}
//CAN发送数据函数(参数:总线名,ID,数据数组,数量。返回值:0成功HAL_OK,1参数错误HAL_ERROR,2发送失败HAL_BUSY)
//示例:CAN1_SendNormalData(&hcan1,0,CAN_buffer,8);//CAN发送数据函数
uint8_t  CAN1_SendNormalData(CAN_HandleTypeDef* hcan,uint16_t ID,uint8_t *pData,uint16_t  Len)
{
    HAL_StatusTypeDef HAL_RetVal;//判断状态的枚举
    uint16_t SendTimes,SendCNT=0;
    uint8_t  FreeTxNum=0;
    uint32_t CAN_TX_BOX0;
    TxMeg.StdId=ID;
    if(!hcan||!pData||!Len){
    	printf("\n\rCAN发送失败!\n\r"); //串口发送
    	return  HAL_ERROR;//如果总线名、数据、数量任何一个为0则返回值为1
    }
    SendTimes=Len/8+(Len%8?1:0);
    FreeTxNum=HAL_CAN_GetTxMailboxesFreeLevel(hcan);//得出空闲邮箱的数量
    TxMeg.DLC=8;
    while(SendTimes--){//循环判断分批发送是否结束
       if(0==SendTimes){//如果分批发送结束
           if(Len%8)TxMeg.DLC=Len%8;//则加入最后不足8个的数据内容
       }
       while(0 == FreeTxNum){
            FreeTxNum = HAL_CAN_GetTxMailboxesFreeLevel(hcan);
        }
//       HAL_Delay(1);//延时防止速度过快导致的发送失败
       //开始发送数据(参数:总线名,设置参数,数据,邮箱号)
       HAL_RetVal=HAL_CAN_AddTxMessage(hcan,&TxMeg,pData+SendCNT,&CAN_TX_BOX0);
       if(HAL_RetVal!=HAL_OK){
    		   printf("\n\rCAN总线忙碌!\n\r"); //串口发送
    		   return  HAL_BUSY;//如果发送失败,则返回值为2
       }
       SendCNT+=8;
    }
    return HAL_OK;//如果发送成功结束,返回值为0
}
//CAN总线通信,使用CAN1,这是CAN专用的printf函数
//调用方法:CAN1_printf("123"); //向UART8发送字符123
void CAN1_printf (char *fmt, ...)
{
    char buff[CAN1_REC_LEN+1];  //用于存放转换后的数据 [长度]
    uint16_t i=0;
    va_list arg_ptr;
    va_start(arg_ptr, fmt);
    vsnprintf(buff, CAN1_REC_LEN+1, fmt,  arg_ptr);//数据转换
    i=strlen(buff);//得出数据长度
    if(strlen(buff)>CAN1_REC_LEN)i=CAN1_REC_LEN;//如果长度大于最大值,则长度等于最大值(多出部分忽略)
    CAN1_SendNormalData(&hcan,0x12,(uint8_t *)buff,i);//CAN发送数据函数(ID为0x12)
    va_end(arg_ptr);
}

main.c

#include "../../icode/can/can1.h"

int main(void)
{

  RetargetInit(&huart1);//将printf()函数映射到UART1串口上
  HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1);//开启串口1接收中断
  HAL_UART_Receive_IT(&huart2,(uint8_t *)&USART2_NewData,1); //开启串口2接收中断
  //  HAL_CAN_MspDeInit(&hcan);//关闭CAN功能,使USB功能可被电脑识别(因USB与CAN共用RAM空间,不能同时使用)
  HAL_CAN_MspInit(&hcan);//开启CAN功能(因USB与CAN共用RAM,不能同时使用,USB用完后想用CAN可在CAN收发前打开)
  CAN_User_Init(&hcan);//CAN1总线用户层初始化 同时开启CAN1功能


  while (1)
  {
      if(CAN1_RX_STA!=0)//CAN判断中断接收标志位【处理从CAN外部设备接收的字符】
      {
    	  BUZZER_SOLO1();//蜂鸣器输出单音的报警音(样式1:HAL库的精准延时函数)
    	  CAN1_printf("%c",CAN1_RX_BUF[0]); //CAN总线发送
    	  CAN1_RX_STA=0;//清除标志位
      }
	  if(KEY_1())//按下KEY1判断
	  {
		  BUZZER_SOLO2();//提示音
		  CAN1_printf("A");//向CAN1发送字符A
	  }
	  if(KEY_2())//按下KEY2判断
	  {
		  BUZZER_SOLO2();//提示音
		  CAN1_printf("B");//向CAN1发送字符B
	  }

   }

}

你可能感兴趣的:(#,STM32,stm32,学习,笔记)