文章目录:
一: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<time1;i++){//循环次数决定单音的时长 HAL_GPIO_WritePin(BEEP1_GPIO_Port,BEEP1_Pin,GPIO_PIN_RESET); //蜂鸣器接口输出低电平0 HAL_Delay(hz1); //延时(毫秒级延时最小1微秒,实现的单调较低,因不需要额外编写微秒级延时函数所以最简单实用) HAL_GPIO_WritePin(BEEP1_GPIO_Port,BEEP1_Pin,GPIO_PIN_SET); //蜂鸣器接口输出高电平1 HAL_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); //蜂鸣器接口输出低电平0 delay_us(hz2); //延时 HAL_GPIO_WritePin(BEEP1_GPIO_Port,BEEP1_Pin,GPIO_PIN_SET); //蜂鸣器接口输出高电平1 delay_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==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 <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 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 <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){//接收到了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;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世纪,年份数加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(*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; 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 <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;//数据标志位清0 memset(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计算结果显示在超级终端 //读取芯片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.外部中断(外部中断的按键测试程序)
设置
点击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调光测试程序
设置
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 <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){//接收到了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 <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;//过滤器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 } } }