win8.1 做网站服务器,自己做的网站无法访问,网站设计规划方案,龙海网站制作学习中遇到的知识点// static 变量只会在第一次运行时初始化为 0#xff0c;之后会一直保留它的值。写在函数中局部变量延长寿命#xff1b;写在全局变量表示这个文件私有 extern也不行。GPIO的引脚输出模式推挽输出理解为很强劲的输出模式#xff0c;可以输出3v3的高电平开漏输出引脚中的3v3开关被砍了也就是只有GND的开关低电平接地高电平悬空相当于断路 这种只有在外部接上上拉电阻到电源才能得到高电平使用场景1、电平转换设置引脚为开漏输出外部接上拉电阻连到5v当输出高电平引脚悬空上拉电阻拉到5v输出为低电平引脚接地为0v2、防止短路 用于i2c总线两块芯片同接一条线一个若输出3v3一个输出0v直接短路烧了。此时用开漏大家只负责拉低电压想接高电压时设开漏由外部电阻拉上去GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_OD中断相关的函数这是一段最常见的完整配置定时器内容#include stm32f1xx_hal.h // 根据你的芯片型号修改例如 stm32f4xx_hal.h /* 全局变量定义 */ TIM_HandleTypeDef htim2; // 定时器句柄必须是全局的以便在中断中访问 /* 函数声明 */ void SystemClock_Config(void); void TIM2_Init(void); void GPIO_Init(void); int main(void) { // 1. 复位所有外设初始化Flash接口和Systick HAL_Init(); // 2. 配置系统时钟 (具体参数取决于你的硬件这里假设已配置好) SystemClock_Config(); // 3. 初始化GPIO GPIO_Init(); // 4. 初始化定时器 TIM2_Init(); while (1) { // 主循环为空所有工作都在中断中完成 } } void TIM2_Init() { // 开启TIM2时钟 __HAL_RCC_TIM2_CLK_ENABLE(); // 配置定时器参数 // 假设APB1总线时钟为 72MHz (STM32F1常见配置) // 目标: 1秒中断一次 // 频率 Clock / ((PSC1) * (ARR1)) // 1Hz 72,000,000 / (7200 * 10000) htim2.Instance TIM2; htim2.Init.Prescaler 7199; // 预分频器 (0-based) - 7200分频 - 10kHz计数频率 htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 9999; // 自动重装载值 (0-based) - 计数10000次 - 1秒 htim2.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(htim2) ! HAL_OK) { // 初始化错误处理 while(1); } // --------------------------------------------------------- // 关键修正配置NVIC (嵌套向量中断控制器) // --------------------------------------------------------- HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0); // 设置中断优先级 HAL_NVIC_EnableIRQ(TIM2_IRQn); // 使能TIM2中断通道 // 启动定时器中断 HAL_TIM_Base_Start_IT(htim2); } void GPIO_Init() { GPIO_InitTypeDef GPIO_InitStruct {0}; // 开启GPIOA时钟 __HAL_RCC_GPIOA_CLK_ENABLE(); // 配置PA0为推挽输出 GPIO_InitStruct.Pin GPIO_PIN_0; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); } // --------------------------------------------------------- // 关键修正中断服务函数 (IRQ Handler) // 这个函数名是固定的通常在 stm32xxxx_it.c 中也可以放在这里 // --------------------------------------------------------- void TIM2_IRQHandler(void) { // 调用HAL库的通用定时器中断处理函数 // 该函数会自动清除中断标志位并调用下面的Callback HAL_TIM_IRQHandler(htim2); } // HAL库的回调函数 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { // 翻转LED HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0); } } // 这是一个空的系统时钟配置实际项目中需要根据你的外部晶振来写 void SystemClock_Config(void) { // 这里通常由CubeMX自动生成 }32中的中断靠的是定时器 首先来聊一下定时器的配置过程我们可以看到上面定时器2的初始过程TIM_HandleTypeDef htim2; // 定时器句柄必须是全局的以便在中断中访问这里是先定义了一个全局的定时器结构体htim2也叫句柄。TIM_HandleTypeDef表示定时器句柄类型。htim2.Instance TIM2;这里表示选择了哪个定时器也就是定时器的名字htim2.Init.Prescaler 7199; // 预分频器 (0-based) - 7200分频 - 10kHz计数频率 htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 9999; // 自动重装载值 (0-based) - 计数10000次 - 1秒这里是定时器的重点分别是预分频和自动重载值用来确定记时长短。我们的单片机总线时钟为72MHZ看单片机的型号确定这里表示1s跳72乘10的6次方次如果我们来计时一秒我们在预分频时可以设置跳7200次记一次即 htim2.Init.Prescaler 7199;从0开始计在进行分频后现在时1s跳10000次接着我们设定重载值为9999即可计数1sHAL_NVIC_SetPriority(TIM2_IRQn, 0, 0); // 设置中断优先级 HAL_NVIC_EnableIRQ(TIM2_IRQn); // 使能TIM2中断通道这里是配置定时器的中断部分。NVIC即中断管理在第一行代码设置中断优先级代码中第一个是抢占优先级第二个是响应优先级第二行代码是中断使能开关定时器虽然计时后会产生信号但是这里如果没有使能信号就传不进CPU中这里是让定时器有资格给CPU发出信号。// 启动定时器中断 HAL_TIM_Base_Start_IT(htim2);这里是开启定时器中断。让CPU愿意接受中断信号。// --------------------------------------------------------- // 中断服务函数 (IRQ Handler) // 这个函数名是固定的通常在 stm32xxxx_it.c 中也可以放在这里 // --------------------------------------------------------- void TIM2_IRQHandler(void) { // 调用HAL库的通用定时器中断处理函数 // 该函数会自动清除中断标志位并调用下面的Callback HAL_TIM_IRQHandler(htim2); } // HAL库的回调函数 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { // 翻转LED HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0); } }中断服务函数TIM2_IRQHandler类似于接待客户的前台HAL_TIM_IRQHandler(htim2)相当于是业务经理你需要将咨询的事件htim2给他在这个过程该函数会查你是哪种中断触发的并且清除标志位。他后面将会为你安排具体解决事件的业务员回调函数中断回调函数HAL_TIM_PeriodElapsedCallback这是真正办事的业务员在这个函数中写具体的中断事件。记得在函数中要判断是哪个定时器。GPIO输入实战场景按键控制 LED 假设按键接在PA0按下时接地低电平松开时通过上拉电阻接高电平。LED 接在PB5。// 在 while(1) 循环中 // 1. 读取 PA0 的状态 if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) GPIO_PIN_RESET) { // 检测到按键按下因为接地了读到低电平 HAL_Delay(20); // 简单的消抖防止机械触点抖动误判 // 再次确认是否真的按下了 if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) GPIO_PIN_RESET) { // 翻转 LED HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5); // 等待按键松开死循环等待防止一直翻转 while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) GPIO_PIN_RESET); } }句柄类型硬件外设句柄类型 (结构体类型)句柄变量名 (通常写法)含义定时器TIM_HandleTypeDefhtim2,htim3定时器2、定时器3的“把手”串口UART_HandleTypeDefhuart1,huart2串口1、串口2的“把手”ADCADC_HandleTypeDefhadc1模数转换器的“把手”SPISPI_HandleTypeDefhspi1SPI总线的“把手”数据类型定义类型名全称字节数范围用途uint8_t小酒杯1 Byte0 ~ 255存字符、标志位、通信数据uint16_t喝水杯2 Bytes0 ~ 65535存 ADC 采样值 (0~4095)、定时器计数uint32_t大扎啤杯4 Bytes0 ~ 42亿存时间戳、内存地址、大数字在C 语言的底层stdint.h头文件里它其实就是给unsigned char起了一个更短、更标准的别名typedef unsigned char uint8_t;STM32 的内存RAM很贵。如果你只需要存一个 0~100 的数字比如年龄、温度、开关状态用int通常占 4 字节太浪费了用uint8_t占 1 字节刚刚好。UART串口补充知识sizeof(msg)问的是这个杯子有多大回答100。strlen(msg)问的是杯子里装了多少水回答5。下面来详细介绍uart串口的初始化发送中断接收、回调以下代码实现的功能是电脑发给单片机什么字符单片机就立刻把这个字符发回给电脑顺便翻转一下 LED 表示收到了。#include main.h #include string.h // 用于 strlen 函数 #include stdio.h // 用于 sprintf 函数 /* 1. 定义全局句柄 (Handle) */ UART_HandleTypeDef huart1; /* 2. 定义收发缓冲区 */ uint8_t rxBuffer[1]; // 接收缓存一次只收1个字符 uint8_t txBuffer[50]; // 发送缓存用来存我们要说的话 /* 函数声明 */ void SystemClock_Config(void); static void MX_USART1_UART_Init(void); static void MX_GPIO_Init(void); int main(void) { /* 1. 基础初始化 */ HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); // 初始化LED引脚 MX_USART1_UART_Init(); // 初始化串口1 /* 2. 发送一句开场白 (证明发送功能正常) */ char msg[] System Ready! Send me a char...\r\n; // 参数句柄数据地址长度超时时间(ms) HAL_UART_Transmit(huart1, (uint8_t*)msg, strlen(msg), 100); /* 3. 【关键】开启第一次接收中断 */ // 告诉串口去监听 1 个字节的数据收到了就存到 rxBuffer 里然后喊我。 HAL_UART_Receive_IT(huart1, (uint8_t *)rxBuffer, 1); while (1) { /* 主循环里什么都不用做全靠中断工作 */ /* 你可以在这里加入延时闪烁证明系统没有卡死 */ // HAL_Delay(1000); } } /* 4. 中断回调函数 (业务逻辑写在这里) */ /* 当数据接收完成(Complete)后HAL库会自动调用这个函数 */ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { // 第一步核对身份是不是串口1叫我 if (huart-Instance USART1) { // 业务 A翻转 LED (PA0)视觉提示 HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0); // 业务 B把收到的字符发回去 (Echo) // 比如收到 A我们就回复 I received: A // 格式化字符串 sprintf((char*)txBuffer, I received: %c\r\n, rxBuffer[0]); // 发送回去 HAL_UART_Transmit(huart1, txBuffer, strlen((char*)txBuffer), 100); // 【最重要的一步】重新开启接收中断 // 因为中断响应一次后会自动关闭必须重新“上膛” HAL_UART_Receive_IT(huart1, (uint8_t *)rxBuffer, 1); } } /* ----------------------------------------------------------- 以下是配置函数通常由CubeMX生成理解即可 ----------------------------------------------------------- */ static void MX_USART1_UART_Init(void) { huart1.Instance USART1; huart1.Init.BaudRate 115200; // 波特率必须和电脑串口助手一致 huart1.Init.WordLength UART_WORDLENGTH_8B; // 8位数据 huart1.Init.StopBits UART_STOPBITS_1; // 1位停止位 huart1.Init.Parity UART_PARITY_NONE; // 无校验 huart1.Init.Mode UART_MODE_TX_RX; // 既能发也能收 huart1.Init.HwFlowCtl UART_HWCONTROL_NONE; // 无硬件流控 huart1.Init.OverSampling UART_OVERSAMPLING_16; // 初始化并检查错误 if (HAL_UART_Init(huart1) ! HAL_OK) { // 初始化失败的处理 while(1); } } /* GPIO 初始化 (假设 LED 在 PA0) */ static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); // 开启时钟 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET); GPIO_InitStruct.Pin GPIO_PIN_0; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); } void SystemClock_Config(void) { // 这里省略时钟配置代码通常由工具生成 }接下来是中断入口stm32f1xx_it.c#include main.h #include stm32f1xx_it.h /* 引入外部定义的句柄告诉编译器 huart1 在别的地方定义过 */ extern UART_HandleTypeDef huart1; /** * brief 这是 USART1 的全局中断入口函数 * 只要串口有动静发完了、收到了CPU 都会跳到这里 */ void USART1_IRQHandler(void) { /* 调用 HAL 库的处理函数 */ /* 它会负责清除标志位并判断是去调 TxCallback 还是 RxCallback */ HAL_UART_IRQHandler(huart1); }我们一点点来看这段代码。// 参数句柄数据地址长度超时时间(ms) HAL_UART_Transmit(huart1, (uint8_t*)msg, strlen(msg), 100);这个是发送的函数在第二个位置放的是需发送的数组地址因为我们的数组msg[ ]是char类型而对于这个发送的函数这里的定义是一个uint8类型这里需要强转一下目前不强转直接发送虽然不会出错但是如果是进行移位操作时是存在出错风险的所以还是按照这个函数的定义来。strlen用法参考开头补充知识。【关键】开启第一次接收中断 */ // 告诉串口去监听 1 个字节的数据收到了就存到 rxBuffer 里然后喊我。 HAL_UART_Receive_IT(huart1, (uint8_t *)rxBuffer, 1);这里是开启接收中断原理是在检测到rebuffer中非空时即跳转中断。这里依旧需要强制类型转换。接下来我们看中断入口里面的内容在串口收到了rebuffer非空发完了txbuffer空了时都会跳转到中断服务函数void USART1_IRQHandler(void)然后HAL_UART_IRQHandler(huart1);会消除标志位并判断跳转到TxCallback 还是 RxCallback。接下来我们看回调函数。void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { // 第一步核对身份是不是串口1叫我 if (huart-Instance USART1) { // 业务 A翻转 LED (PA0)视觉提示 HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0); // 业务 B把收到的字符发回去 (Echo) // 比如收到 A我们就回复 I received: A // 格式化字符串 sprintf((char*)txBuffer, I received: %c\r\n, rxBuffer[0]); // 发送回去 HAL_UART_Transmit(huart1, txBuffer, strlen((char*)txBuffer), 100); // 【最重要的一步】重新开启接收中断 // 因为中断响应一次后会自动关闭必须重新“上膛” HAL_UART_Receive_IT(huart1, (uint8_t *)rxBuffer, 1); } }这里首先依旧是判断为串口1我们在这里进行翻转LED并将得到的字符发送回去。这里介绍一下sprintf他有三个参数第二个相当于是模板第三个相当于是参数二三组合在一起可以构成我们需要发送的句子然后会存在第一个参数的数组里面。这里强转为char类型。后面利用HAL_UART_Transmit(huart1, txBuffer, strlen((char*)txBuffer), 100);发送出去。在最后一行我们看见又重新写了 HAL_UART_Receive_IT(huart1, (uint8_t *)rxBuffer, 1);是因为这个接收中断只能执行一次就会失效所以我们这里在回调函数的最后写可以保证它后续可以继续产生中断响应。这里的发送并没有用中断发送后续再讲解。[Main函数] | |---- 调用 HAL_UART_Transmit_IT (准备数据开中断) --- | | | (CPU 继续跑别的代码不傻等) | | | | v [中断发生!] ------------------------------------ [硬件: 发送寄存器空了] | ---- [中断服务函数 HAL_UART_IRQHandler] (干活主力) | | | --- 搬运 1 个字节 | --- 还有剩余吗 | |-- 有: 退出中断等下一次硬件喊我 | | | |-- 没有(发完了): 关中断 - 调用回调函数 ---- | | [回到Main] v [回调函数 TxCpltCallback] (打卡下班)是发完所有信息后才跳到中断回调函数吗是的绝对是。 哪怕你发 10000 个字节只有当第 10000 个字节也被发走的那一瞬间回调函数 (TxCpltCallback) 才会被调用。是哪个函数让 CPU 去执行发送任务的主要苦力是中断服务函数PWM脉宽调制寄存器中的位操作这里是单片机中的几个常用寄存器缩写全称含义CRConfiguration Register配置寄存器CTRLControl Register控制寄存器SRStatus Register状态寄存器DRData Register数据寄存器PUPDRPull-Up/Pull-Down Register上拉下拉寄存器常用的设置寄存器值的操作1、清零操作 GPIOA-CRL 0xFFFFFF0F; /* 将第 4~7 位清 0 */0xFFFFFF0F写成二进制为 1111 1111 1111 1111 1111 1111 0000 1111 当寄存器的值与该值按位与操作后 该值为一的地方寄存器中的值保持不变 该值为0的地方寄存器中的值清零。2、设值操作 |GPIOA-CRL | 0x00000040; /* 设置相应位的值(4)不改变其他位的值 */同理 这里将其写成二进制数 只有第6位为1 其余全为0 按位或操作后 只有第六位被置1 其余值不变3、移位操作 // 操作SysTick-CTRL寄存器的第2位时钟源选择位 SysTick-CTRL | 1 2;12 表示对0b1进行左移两位的操作 即得0b100接着就是进行按位或的操作置1 这里是将第4位置为14、^ 表示异或 ~ 表示按位取反