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;//如果未进入按键处理,则返回0if(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;//如果未进入按键处理,则返回0if(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<time1;i++){//循环次数决定单音的时长HAL_GPIO_WritePin(BEEP1_GPIO_Port,BEEP1_Pin,GPIO_PIN_RESET); //蜂鸣器接口输出低电平0HAL_Delay(hz1); //延时(毫秒级延时最小1微秒,实现的单调较低,因不需要额外编写微秒级延时函数所以最简单实用)HAL_GPIO_WritePin(BEEP1_GPIO_Port,BEEP1_Pin,GPIO_PIN_SET); //蜂鸣器接口输出高电平1HAL_Delay(hz1); //延时}
}
#define time2 200 //单音的时长
#define hz2 500 //单音的音调(单位微秒)
void BUZZER_SOLO2(void){//蜂鸣器输出单音的报警音(样式2:CPU微秒级延时)uint16_t i;for(i=0;i<time2;i++){//循环次数决定单音的时长HAL_GPIO_WritePin(BEEP1_GPIO_Port,BEEP1_Pin,GPIO_PIN_RESET); //蜂鸣器接口输出低电平0delay_us(hz2); //延时HAL_GPIO_WritePin(BEEP1_GPIO_Port,BEEP1_Pin,GPIO_PIN_SET); //蜂鸣器接口输出高电平1delay_us(hz2); //延时}
}

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==0x0001if(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 <sys/stat.h>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 <errno.h>
#include <sys/time.h>
#include <sys/times.h>
#include <limits.h>
#include <signal.h>
#include <../Inc/retarget.h>
#include <stdint.h>
#include <stdio.h>
#if !defined(OS_USE_SEMIHOSTING)
#define STDIN_FILENO  0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2UART_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;elsereturn 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;elsereturn 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 <string.h>//用于字符串处理的库
#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){//接收到了0x0dif(USART1_NewData!=0x0a)USART1_RX_STA=0;//接收错误,重新开始else USART1_RX_STA|=0x8000;   //接收完成了}else{ //还没收到0X0Dif(USART1_NewData==0x0d)USART1_RX_STA|=0x4000;else{USART1_RX_BUF[USART1_RX_STA&0X3FFF]=USART1_NewData; //将收到的数据放入数组USART1_RX_STA++;  //数据长度计数加1if(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_IN5Analog功能组中——>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_IN5Analog功能组中——>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和通道5HAL_Delay(500);//在主循环里写入HAL库的毫秒级延时函数
}

五:RTC与BKP驱动程序

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

设置

开启RTC功能打开时钟数视图——>RTC的时钟这里选择LSE外部低速时钟LSE——>频率为32.7⑥8KHzTimers——>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个//将超级终端发过来的数据换算并写入RTCRtcDate.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();//使能电源时钟PWRHAL_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_IN5Analog功能组中——>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个//将超级终端发过来的数据换算并写入RTCryear = (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-2099for(t=1970;t<syear;t++){ //把所有年份的秒钟相加if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数else seccount+=31536000;                    //平年的秒钟数}smon-=1;for(t=0;t<smon;t++){         //把前面月份的秒钟数相加seccount+=(uint32_t)mon_table[t]*86400;//月份秒钟数相加if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数}seccount+=(uint32_t)(sday-1)*86400;//把前面日期的秒钟数相加seccount+=(uint32_t)hour*3600;//小时秒钟数seccount+=(uint32_t)min*60;      //分钟秒钟数seccount+=sec;//最后的秒钟加上去//【寄存器操作】因为HAL库的不完善,无法直接调用RTC_ReadTimeCounter函数。此处改用寄存器直接操作。RTC->CRL|=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世纪,年份数加100if (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); //拉低至少18msHAL_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~80usretry++;delay_us(1);}if(retry>=100)return 1; else retry=0;while (!HAL_GPIO_ReadPin(GPIOB,DHT11_DA_Pin)&&retry<100){//DHT11拉低后会再次拉高40~80usretry++;delay_us(1);}if(retry>=100)return 1;return 0;
}uint8_t Dht11_ReadBit(void){ //从DHT11读取一个位 返回值:1/0uint8_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或0if(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]; //将湿度值放入指针1h++;*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 SettingMotorola    帧模式:摩托罗拉8Bits       数据大小:8位MSB First   首位:MSB起始2           分频器:218.0 MBits/s波特率:18.OMBHighHigh         高2 Edge       CPHA:2边沿Disabled     CRC校验:禁用Software     使能信号:软件——>NVIC Setting——>勾选中断SPI12 global interruptSystem 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;//定义一个变量RxdataHAL_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;//定义一个变量tempW25QXX_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]);//显示芯片IDBUZZER_SOLO1();//提示音}if(KEY_2()){BUZZER_SOLO1();//提示音W25QXX_Read(EX_FLASH_BUF,EX_FLASH_ADD,1);//读出W25QXX芯片数据(参数:读出数据存放的数组,读取的开始地址,数量)EX_FLASH_BUF[0]++;//数据加1if(EX_FLASH_BUF[0]>200)EX_FLASH_BUF[0]=0;//如果数值大于指定最大值则清0W25QXX_Write(EX_FLASH_BUF,EX_FLASH_ADD,1);//写入W25QXX芯片数据(参数:读出数据存放的数组,读取的开始地址,数量)printf("读出0x00地址数据:%d \n\r",EX_FLASH_BUF[0]);//读出数据BUZZER_SOLO1();//提示音}}} 

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

设置

时钟树视图——>To USB使USB最终频率为48MHzConnectivity——>USB——>勾选Device设备——>设置端口为USB接口PA12 USB_DP;PA12 USB_DP——>点击参数设置Parameter Setting——>参数按默认设置——>MVIC Setting——>勾选USB低优先级USB low priority or CAN RX0 interruptsMiddleware——>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 SettingsMinimum Heap Size  0x1000Minimum 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(*Len<USB_REC_LEN)//判断收到数据量是否小于寄存器上限{uint16_t i;USB_RX_STA = *Len;//将数据量值放入标志位for(i=0;i<*Len;i++)//循环(循环次数=数据数量)USB_RX_BUF[i] = Buf[i];//将数据内容放入数据寄存器}USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);USBD_CDC_ReceivePacket(&hUsbDeviceFS);return (USBD_OK);/* USER CODE END 6 */
}uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
{uint8_t result = USBD_OK;/* USER CODE BEGIN 7 */uint32_t TimeStart = HAL_GetTick();USBD_CDC_HandleTypeDef *hcdc =  (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;//if (hcdc->TxState != 0) return  USBD_BUSY;while(hcdc->TxState){if(HAL_GetTick()-TimeStart > 10)return USBD_BUSY;elsebreak;}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 <stdarg.h>
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;//数据标志位清0memset(USB_RX_BUF,0,sizeof(USB_RX_BUF));//USB串口数据寄存器清0}}}

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

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计算结果显示在超级终端//读取芯片IDa = *(__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进制IDwhile (1);//执行结束后在此循环}{

十:外部中断与定时器

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

 

 设置

点击PA0——>设置为外部中断GPIO_EX10System 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;//如果未进入按键处理,则返回0if(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;//如果未进入按键处理,则返回0if(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 SettingsPrescaler(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调光测试程序

 设置

PB0——>设置为TM3_CH3Timers——>TIM3——>勾选内部时钟Internal Clock——>Channel3设置为PWM Generation CH3Parameter——>Counter SettingsPrescaler (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 Channal3Mode                       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++;//占空比值加1if(a>=499)a=0;//当占空比值达到最大后清0HAL_Delay(10);//在主循环每10毫秒循环一次}
}

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

设置

将PA2设置为——>USAR_T2端口
将PA3设置为——>SAR_RX端口Connecticity——>USART2——>Mode设置为异步模式Asynchronous——>Parameter Settings参数设置Baud Rate        波特率        115200 Bits/sWord Length      位宽度        8 Bits (including Parity)Parity           校验          NoneStop Bits        停止位        1Data Direction    数据方向    Receive and TransmitOver Sampling     采样        16 Samples——>NMIC Settings——>勾选运行中断USART2 global interruptSystem 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 <string.h>//用于字符串处理的库
#include <stdarg.h>
#include <stdlib.h>
#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){//接收到了0x0dif(USART1_NewData!=0x0a)USART1_RX_STA=0;//接收错误,重新开始else USART1_RX_STA|=0x8000;   //接收完成了}else{ //还没收到0X0Dif(USART1_NewData==0x0d)USART1_RX_STA|=0x4000;else{USART1_RX_BUF[USART1_RX_STA&0X3FFF]=USART1_NewData; //将收到的数据放入数组USART1_RX_STA++;  //数据长度计数加1if(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++;//数据接收标志位加1HAL_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 SettingBit Timings ParametersPrescaler (for Time Quantum)            9Time Quantum                            250.0nsTime Quanta in Bit Segment 1            8 TimesTime Quanta in Bit Segment 2            7 TimesTime for one Bit                        4000 nsBaud Rate                               250000 bi/sReSynchronization Jump Width            1 TimeBasic ParametersTime Triggered Communication Mode       DisableAutomatic Bus-Off Management            DisableAutomatic Wake-Up Mode                  DisableAutomatic Retransmission                DisableReceive Fifo Locked Mode                DisableTransmit Fifo Priority                  DisableAdvanced ParametersOperating Mode                          Normal——>MVIC Settings——>勾选CAN RX1

can文件夹

can1.h

#ifndef CAN_CAN1_H_
#define CAN_CAN1_H_#include "stm32f1xx_hal.h" //HAL库文件声明
#include <string.h>//用于字符串处理的库
#include <stdarg.h>
#include <stdlib.h>
#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;//过滤器0sFilterConfig.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}}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/92737.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

“MongoDB基础知识【超详细】

"探索MongoDB的无边之境&#xff1a;沉浸式数据库之旅" 欢迎来到MongoDB的精彩世界&#xff01;在这个博客中&#xff0c;我们将带您进入一个充满创新和无限潜力的数据库领域。无论您是开发者、数据工程师还是技术爱好者&#xff0c;MongoDB都将为您带来一场令人心动…

PLY模型格式详解【3D】

本文介绍PLY 多边形文件格式&#xff0c;这是一种用于存储被描述为多边形集合的图形对象。 PLY文件格式的目标是提供一种简单且易于实现但通用的格式足以适用于各种模型。 PLY有两种子格式&#xff1a;易于入门的 ASCII 表示形式和用于紧凑存储和快速保存和加载的二进制格式。 …

搭建 Python 环境 | Python、PyCharm

计算机 计算机能完成的工作&#xff1a; 算术运算逻辑判断数据存储网络通信…更多的更复杂的任务 以下这些都可以称为 “计算机”&#xff1a; 一台计算机主要由以下这几个重要的组件构成 CPU 中央处理器&#xff1a;大脑&#xff0c;算术运算&#xff0c;逻辑判断 存储器&…

Redis——常见数据结构与单线程模型

Redis中的数据结构 Redis中所有的数据都是基于key&#xff0c;value实现的&#xff0c;这里的数据结构指的是value有不同的类型。 当前版本Redis支持10种数据类型&#xff0c;下面介绍常用的五种数据类型 底层编码 Redis在实现上述数据结构时&#xff0c;会在源码有特定的…

Docker数据卷容器

1.数据卷容器介绍 即使数据卷容器c3挂掉也不会影响c1和c2通信。 2.配置数据卷容器 创建启动c3数据卷容器&#xff0c;使用-v参数设置数据卷。volume为目录&#xff0c;这种方式数据卷目录就不用写了&#xff0c;直接写宿主机目录。 创建c1、c2容器&#xff0c;使用–volum…

三星霸主地位“无可撼动“,DRAM内存市场份额创近 9 年新低仍第一

三星电子在DRAM市场的竞争地位一直备受关注。据报告显示&#xff0c;除了市场份额下降外&#xff0c;三星电子在上半年的销售额也出现了下滑。这主要是由于全球消费电子产品需求下滑&#xff0c;导致三星电子的芯片需求减少。 存储芯片业务所在的设备解决方案部门的营收和利润也…

24届近3年上海电力大学自动化考研院校分析

今天给大家带来的是上海电力大学控制考研分析 满满干货&#xff5e;还不快快点赞收藏 一、上海电力大学 学校简介 上海电力大学&#xff08;Shanghai University of Electric Power&#xff09;&#xff0c;位于上海市&#xff0c;是中央与上海市共建、以上海市管理为主的全日…

经典人体模型SMPL介绍(一)

SMPL是马普所提出的经典人体模型&#xff0c;目前已成为姿态估计、人体重建等领域必不可少的基础先验。SMPL基于蒙皮和BlendShape实现&#xff0c;从数千个三维人体扫描结果得来&#xff0c;后通过PCA统计学习得来。 论文&#xff1a;SMPL: A Skinned Multi-Person Linear Mode…

基于Python的HTTP代理爬虫开发初探

前言 随着互联网的发展&#xff0c;爬虫技术已经成为了信息采集、数据分析的重要手段。然而在进行爬虫开发的过程中&#xff0c;由于个人或机构的目的不同&#xff0c;也会面临一些访问限制或者防护措施。这时候&#xff0c;使用HTTP代理爬虫可以有效地解决这些问题&#xff0…

普华永道踩坑MOVEit漏洞,泄露银行8万名储户的信息

8月14日&#xff0c;波多黎各自治区最大的银行——人民银行向缅因州司法部长提交了一份客户信息泄露报告。该报告指出&#xff0c;由于供应商普华永道使用的MOVEit软件存在安全漏洞&#xff0c;导致银行82217名储户的个人信息被泄露。 目前&#xff0c;波多黎各人民银行已经陆续…

javaScript:数组方法(增删/提取类/截取/操作方法等)

目录 一.数组的增删方法 1.push()数组末尾添加元素 解释 代码 运行截图 2.unshift()向数组的头部添加数组 解释 代码 运行截图 3.pop()数组的尾部删除一个元素 解释 代码 运行截图 4.shift()数组的头部删除一个元素 解释 代码 运行截图 5. splice()任意位…

使用 Visual Studio Code 调试 CMake 脚本

之前被引入到 Visual Studio 中的 CMake 调试器&#xff0c;现已在 Visual Studio Code 中可用。 也就是说&#xff0c;现在你可以通过在 VS Code 中安装 CMake 工具扩展&#xff0c;来调试你的 CMakeLists.txt 脚本了。是不是很棒? 背景知识 Visual C 开发团队和 CMake 的维…

最全的【DDD领域建模】小白学习手册(文末附资料)

1、前言&#xff1a; 在当时的环境下&#xff0c;单体应用仍然是市场的主体&#xff0c;但是大型复杂软件系统已经出现&#xff0c;给团队的设计和开发工作带来了比较大的挑战。 DDD提供了一种新的设计思路&#xff0c;通过对于业务子域和限界上下文的划分&#xff0c;建立跨…

【Oracle 数据库 SQL 语句 】积累1

Oracle 数据库 SQL 语句 1、分组之后再合计2、显示不为空的值 1、分组之后再合计 关键字&#xff1a; grouping sets &#xff08;&#xff08;分组字段1&#xff0c;分组字段2&#xff09;&#xff0c;&#xff08;&#xff09;&#xff09; select sylbdm ,count(sylbmc) a…

【数据结构OJ题】链表中倒数第k个结点

原题链接&#xff1a;https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId13&&tqId11167&rp2&ru/activity/oj&qru/ta/coding-interviews/question-ranking 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 …

进程间通信——信号

信号的概念 信号是 Linux进程间通信的最古老的方式之一&#xff0c;是事件发生时对进程的通知机制&#xff0c;有时也称之为软件中断&#xff0c;它是在软件层次上对中断机制的一种模拟&#xff0c;是一种异步通信的方式。信号可以导致一个正在运行的进程被另一个正在运行的异…

手机商城网站的分析与设计(论文+源码)_kaic

目录 摘 要 1 1 绪论 2 1.1选题背景意义 2 1.2国内外研究现状 2 1.2.1国内研究现状 2 1.2.2国外研究现状 3 1.3研究内容 3 2 网上手机商城网站相关技术 4 2.1.NET框架 4 2.2Access数据库 4 2.3 JavaScript技术 4 3网上手机商城网站分析与设…

复古游戏库管理器RomM

什么是 RomM &#xff1f; RomM&#xff08;代表 Rom Manager&#xff09;是一个专注于复古游戏的游戏库管理器。通过 Web 浏览器管理和组织您的所有游戏。受 Jellyfin 的启发&#xff0c;允许您从现代界面管理所有游戏&#xff0c;同时使用 IGDB 元数据丰富它们。 RomM 支持的…

线上通过Nginx部署前端工程,并且配置SSL

介绍、为了更好的帮助大家学习&#xff0c;减少歧义,IP地址我就不隐藏了&#xff0c;公司也是我自己的公司。你们就别来攻击了。 下面给出步骤: 一、前期准备工作 通过在目标服务器上安装宝塔面板、安装redis、mysql、nginx、jdk环境等 1、 2、前端工程通过npm run build 打…