完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
前言
我自己刚刚开始学习STM32时,跟随正点原子课程,一节节课慢慢学,裸机开发可以深入了解和学习到寄存器内部,但是也偏无聊一点。后来,在做项目时,发现很难选择芯片型号,一直使用F103C8T6这款芯片。随着2021年芯片价格翻了接近30倍,自己觉得选型芯片的无比重要,遂学习STM32CubeMX。起初HAL库令自己很难适应,毕竟写个一个延时还得找资料,但是随着学习,发现CubeMX对项目的帮助是巨大的,它可以帮助你不受芯片型号的限制,快速开发项目,且内置FreeRTOS,随手就能跑个操作系统,简直不要太香。 不过界面至今没有汉化版,也是唯一的遗憾。如果只学习HAL库的话,可能会有不知底层是何为的困惑,但是也无妨。就像我作为物理学的学生,老师常说只要会用数学公式一样,我们会用即可。 这是自己编写的一个打地鼠的小游戏,串口显示程序运行位置,右方小灯显示分数,达到17分进入彩蛋程序。 Proteus 8 配置工程 使用Proteus 8可以仿真STM32F103系列T4、T6、C4、C6、R4、R6单片机,可以帮助项目前期少走一点弯路,学习者前期可以少花点钱去学习STM32。 使用STM32CubeMX配置基础工程的部分不做讲解,因为图形化真的很简单。本文章主要记录在配置工程后,HAL库函数的使用。 1、GPIO 读取IO: HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin); HAL_GPIO_ReadPin(GPIOA,BUTTONO_Pin); 写入IO: HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState); HAL_GPIO_WritePin(GPIOA,LED0_Pin,GPIO_PIN_RESET) //置0; HAL_GPIO_WritePin(GPIOA,LED0_Pin,GPIO_PIN_SET) //置1; 翻转IO: HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin); HAL_GPIO_TogglePin(GPIOA, LED0_Pin); 按键例子: if(HAL_GPIO_ReadPin(GPIOA,BUTTONO_Pin) == GPIO_Pin_SET); { while(HAL_GPIO_ReadPin(GPIOA,BUTTONO_Pin) == GPIO_Pin_SET);//等待按键抬起 HAL_GPIO_TogglePin(GPIOA, LED0_Pin); //翻转 HAL_Delay(200); //延时200ms HAL_GPIO_TogglePin(GPIOA, LED0_Pin); //翻转 } 2、串口通信 串口通信 串口通信模式: Asynchronous:异步通信 发送、接收数据 //发送数据 HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout); uint8_t temp[] = "Hello World!nr"; HAL_UART_Transmit(&huart1,temp,12,50); HAL_UART_Transmit((UART_HandleTypeDef *) &huart1, (uint8_t *) "Hello World!rn", (uint16_t) 14, (uint32_t) 30); //接收数据 HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout); printf 重定向 在Private includes中引入 #include 在USER CODE BEGIN 0 添加 int fputc(int ch,FILE *f){ uint8_t temp[1]={ch}; HAL_UART_Transmit(&huart1,temp,1,2); //h-uart1需要根据自己的配置修改 return ch; } 然后就可以在任意地方使用printf语句方便的输出你想要的内容。 printf("Hello World!nr"); HAL_Delay(200); 多个串口发送数据 #include #include "stdarg.h" #include "string.h" #define TXBUF_SIZE_MAX 100 void uart?_printf(const char *format, ...) { va_list args; uint32_t length; uint8_t txbuf[TXBUF_SIZE_MAX] = {0}; va_start(args, format); length = vsnprintf((char *)txbuf, sizeof(txbuf), (char *)format, args); va_end(args); HAL_UART_Transmit(&huart?, (uint8_t *)txbuf, length, HAL_MAX_DELAY); memset(txbuf, 0, TXBUF_SIZE_MAX); } scanf重定向 int fgetc(FILE *f) { uint8_t ch = 0; HAL_UART_Receive(&huart1, &ch, 1, 0xffff); return ch; } scanf("%d %d",&a,&b); 多个串口接收信息-回调函数 开启中断,只起一次作用 HAL_UART_Receive_IT(&huart1,(uint8_t *)aRxBuffer1,1); HAL_UART_Receive_IT(&huart2,(uint8_t *)aRxBuffer2,1); 回调函数 #define USART1_RXBUFF_SIZE 1024 //定义串口2 接收缓冲区大小 1024字节 char USART1_RXBUFF[USART1_RXBUFF_SIZE]; //定义接收数组 uint8_t data; uint8_t t=0; uint8_t aRxBuffer1[1]; //定义临时存放数组 uint8_t aRxBuffer2[1]; //定义临时存放数组 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART1) // 判断是由哪个串口触发的中断 { data = aRxBuffer1[0]; USART1_RXBUFF[t] = data; t++; HAL_UART_Receive_IT(&huart1,aRxBuffer1,1); // 重新使能串口1接收中断 } if(huart->Instance == USART2) { HAL_UART_Transmit(&huart2,aRxBuffer2,1,100); // 接收到数据马上使用串口1发送出去 HAL_UART_Receive_IT(&huart2,aRxBuffer2,1); // 重新使能串口2接收中断 } } while函数输出 HAL_Delay(2000); int i; for(i=0;i t=0; 例2 #define USART1_RXBUFF_SIZE 256 //最大接收字节数 #define USART2_RXBUFF_SIZE 256 //最大接收字节数 char Usart1_RxBuff[USART1_RXBUFF_SIZE]; //接收数据 char Usart2_RxBuff[USART2_RXBUFF_SIZE]; //接收数据 uint8_t aRxBuffer1; //接收中断缓冲 uint8_t aRxBuffer2; //接收中断缓冲 uint8_t Uart1_Rx_Cnt = 0; //接收缓冲计数 uint8_t Uart2_Rx_Cnt = 0; //接收缓冲计数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //串口回调函数 { if(huart->Instance == USART1) // 判断是由哪个串口触发的中断 { // HAL_UART_Transmit(&huart1,(uint8_t *)&aRxBuffer1,1,100); // 接收到数据马上使用串口1发送出去 // HAL_UART_Transmit(&huart2,(uint8_t *)&aRxBuffer1,1,100); // 接收到数据马上使用串口2发送出去 // HAL_UART_Receive_IT(&huart1,(uint8_t *)&aRxBuffer1,1); // 重新使能串口1接收中断 if(Uart1_Rx_Cnt >= 255) //溢出判断 { Uart1_Rx_Cnt = 0; memset(Usart1_RxBuff,0x00,sizeof(Usart1_RxBuff)); HAL_UART_Transmit(&huart1, (uint8_t *)"数据溢出", 10,0xFFFF); } else { Usart1_RxBuff[Uart1_Rx_Cnt++] = aRxBuffer1; //接收数据转存 if((Usart1_RxBuff[Uart1_Rx_Cnt-1] == 0x0A)&&(Usart1_RxBuff[Uart1_Rx_Cnt-2] == 0x0D)) //判断结束位 { HAL_UART_Transmit(&huart1, (uint8_t *)&Usart1_RxBuff, Uart1_Rx_Cnt,0xFFFF); //将收到的信息发送出去 while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束 HAL_UART_Transmit(&huart2, (uint8_t *)&Usart1_RxBuff, Uart1_Rx_Cnt,0xFFFF); //将收到的信息发送出去 while(HAL_UART_GetState(&huart2) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束 Uart1_Rx_Cnt = 0; memset(Usart1_RxBuff,0x00,sizeof(Usart1_RxBuff)); //清空数组 } } } HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer1, 1); //再开启接收中断 if(huart->Instance == USART2) { // HAL_UART_Transmit(&huart1,(uint8_t *)&aRxBuffer2,1,100); // 接收到数据马上使用串口1发送出去 if(Uart2_Rx_Cnt >= 255) //溢出判断 { Uart2_Rx_Cnt = 0; memset(Usart2_RxBuff,0x00,sizeof(Usart2_RxBuff)); HAL_UART_Transmit(&huart1, (uint8_t *)"数据溢出", 10,0xFFFF); } else { Usart2_RxBuff[Uart2_Rx_Cnt++] = aRxBuffer2; //接收数据转存 if((Usart2_RxBuff[Uart2_Rx_Cnt-1] == 0x4B)&&(Usart2_RxBuff[Uart2_Rx_Cnt-2] == 0x4F)) //判断结束位“OK” { HAL_UART_Transmit(&huart1, (uint8_t *)&Usart2_RxBuff, Uart2_Rx_Cnt,0xFFFF); //将收到的信息发送出去 while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束 Uart2_Rx_Cnt = 0; memset(Usart2_RxBuff,0x00,sizeof(Usart2_RxBuff)); //清空数组 } } } HAL_UART_Receive_IT(&huart2, (uint8_t *)&aRxBuffer2, 1); //再开启接收中断 } strstr()函数检索信息 函数的声明 char *strstr(const char *haystack, const char *needle) 函数的声明 char *strstr(const char *haystack, const char *needle) [tr]参数说明[/tr]
实例 #include #include int main() { const char haystack[20] = "RUNOOB"; const char needle[10] = "NOOB"; char *ret; ret = strstr(haystack, needle); printf("子字符串是: %sn", ret); return(0); } char *tim_strl = strstr((char *)MQTT_CMDOutPtr+2,"timing":"); //strstr返回字符首次出现的地址 if((int)*(tim_strl+9) == 125) //时间为0 - 9 { timing = (int)(*(tim_strl+8)) - 48; //char转化为int }else if((int)*(tim_strl+10) == 125) { timing = ((int)(*(tim_strl+8))-48)*10 + ((int)(*(tim_strl+9)) - 48); //char转化为int }else if((int)*(tim_strl+11) == 125) { timing = ((int)(*(tim_strl+8))-48)*100 + ((int)(*(tim_strl+9))-48)*10 + ((int)(*(tim_strl+10))-48); } sprintf(temp,"{"method":"thing.event.property.post","id":"203302322","params":{"timing":%d,"currentstate":1},"version":"1.0.0"}",timing); //需要回复状态给服务器 MQTT_PublishQs0(P_TOPIC_NAME,temp,strlen(temp)); //添加数据,发布给服务器 memset()清空接收缓存区 memset(USART1_RXBUFF,0,USART1_RXBUFF_SIZE); //清空WiFi接收缓冲区 每次发送数据前,先情况一下缓存区 LOG信息打印 主流嵌入式输出格式:[日志级别] 文件名: 日志信息 "[info] main.c : init ok!" "[debug] adc.c : adc_getvalue -> 3.3V" printf("[info] main.c : HAL_Init ok! rn"); 条件编译 在单片机开发过程中,需要大量的LOG信息;但是开发结束后,不需要一直打印(拖慢单片机速度)。 所以在main.h头文件添加: #define Log 1 //打印Log信息,不想打印时改为0即可 再把.c文件中所有printf语句包裹上#id Log 与 #endif: #if Log printf("[info] main.c : HAL_Init ok! rn"); # endif 个性化串口输出 字符转ASCII码网站: 个性化 #define Log 1 //打印Log信息,不想打印时改为0即可 #if Log printf(" _____ ______ _______ _ _ _____ _ _ rn"); printf(" | __ )| ____|__ __| | | | __ )( ) | |rn"); printf(" | |__) | |__ | | | | | | |__) | )| |rn"); printf(" | _ /| __| | | | | | | _ /| . ` |rn"); printf(" | | ) )| |____ | | | |__| | | ) (| |( |rn"); printf(" |_| )_)______| |_| (_____/|_| )_(_| (_|rn"); # endif 可变参数宏 在Private includes中引入 #include 1 在USER CODE BEGIN 0 添加 int fputc(int ch,FILE *f){ uint8_t temp[1]={ch}; HAL_UART_Transmit(&huart1,temp,1,2); //h-uart1需要根据自己的配置修改 return ch; } 在USER CODE BEGIN 0 添加 #define USER_LOG //注释此行,不打印 #ifdef USER_LOG #define user_main_printf(format,...) printf(format "rn",##__VA_ARGS__) #define user_main_info(format,...) printf("[main]info:" format "rn",##__VA_ARGS__) #define user_main_debug(format,...) printf("[main]debug:" format "rn",##__VA_ARGS__) #define user_main_error(format,...) printf("[main]error:" format "rn",##__VA_ARGS__) #else #define user_main_printf(format,...) #define user_main_info(format,...) #define user_main_debug(format,...) #define user_main_error(format,...) #endif 在while()中添加 user_main_info("Hello World!"); HAL_Delay(200); 自动添加报头和报尾 发送、接收中断 //发送中断 HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); //接收中断 HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); DMA //使用DMA发送 HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); //使用DMA接收 HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); //DMA暂停 HAL_UART_DMAPause(UART_HandleTypeDef *huart); //DMA恢复 HAL_UART_DMAResume(UART_HandleTypeDef *huart); //DMA停止 HAL_UART_DMAStop(UART_HandleTypeDef *huart); 3、外部中断 回调函数 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin == BUTTON0_Pin); //多个外部中断,需要进一步判断 } 使能 HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim); HAL_TIM_Base_Start_IT(&htim1); //定时器1使能 回调函数 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == htim1.Instance) { //定时器1中断业务 } } 读取定时器1的数值 int time_num = __HAL_TIM_GET_COUNTER(&htim1); //读取定时器1的数值 5、IIC 特点:简单、双向、二线制、同步串行总线 注意:IIC是为了与低速设备通信而发明的,所以IIC的传输速率比不上SPI I2C串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL。所有接到I2C总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。 每个连接到总线的设备都有一个独立的地址,主机正是利用该地址对设备进行访问 IIC写函数 HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); [tr]参数功能说明例[/tr]
HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_I2C_Master_Receive(&hi2c1,0xA1,(uint8_t*)TxData,2,1000) ;; [tr]参数功能说明例[/tr]
HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout); [tr]参数功能说明例[/tr]
#define ADDR_24LCxx_Write 0xA0 #define ADDR_24LCxx_Read 0xA1 #define BufferSize 256 uint8_t WriteBuffer[BufferSize],ReadBuffer[BufferSize]; for(i=0;i HAL_I2C_Mem_Write(&hi2c1, ADDR_24LCxx_Write,i, I2C_MEMADD_SIZE_8BIT,&WriteBuffer,1,0xff); //使用I2C块读,出错。因此采用此种方式,逐个单字节写入 } HAL_I2C_Mem_Read(&hi2c1, ADDR_24LCxx_Read, 0, I2C_MEMADD_SIZE_8BIT,ReadBuffer,BufferSize, 0xff); 6、SPI SPI 共包含 4 条总线。 环形总线结构 两个简单的移位寄存器,传输的数据为8位,根据上面的传输模式发出一位数据就会接收到一位数据,移位寄存器在旧的数据没有发完之前是不会接受新的数据的。 SS(Slave Select):片选信号线,当有多个SPI 设备与 MCU 相连时,每个设备的这个片选信号线是与 MCU 单独的引脚相连的,而其他的 SCK、MOSI、MISO 线则为多个设备并联到相同的 SPI 总线上,低电平有效。 SCK (Serial Clock):时钟信号线,由主通信设备产生,不同的设备支持的时钟频率不一样,如 STM32 的 SPI 时钟频率最大为 f PCLK /2。 MOSI (Master Output Slave Input):主设备输出 / 从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入数据,即这条线上数据的方向为主机到从机。 MISO(Master Input Slave Output):主设备输入 / 从设备输出引脚。主机从这条信号线读入数据,从机的数据则由这条信号线输出,即在这条线上数据的方向为从机到主机。 (1)打开软件,选择对应芯片后,配置好时钟源; (2)勾选SPI1为全双工,硬件NSS关闭,如下图: (3)勾选好后,PA5、PA6、PA7如下图,在配置PA4为普通io口,gpio_output (4)SPI1的参数配置选择默认,如下图所示 HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout); //发送数据 HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout); //接收数据 7、FreeRTOS (1)使能FreeRTOS (2)创建两个FreeRTOS任务:Task1和Task2 配置流程 |
||
|
||
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1792 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1626 浏览 1 评论
1094 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
732 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1682 浏览 2 评论
1943浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
743浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
578浏览 3评论
601浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
563浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-26 18:43 , Processed in 0.960671 second(s), Total 78, Slave 62 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号