当前位置: 首页 > news >正文

自己如何做企业网站做行业导航网站好

自己如何做企业网站,做行业导航网站好,wordpress页面无法编辑,短视频制作软件系列文章目录 留空 文章目录 系列文章目录前言一、LED模块1.1 赛题要求1.2 模块原理图1.3 编写代码1.4 赛题实战 二、LCD模块2.1 赛题要求2.2 模块原理图2.3 编写代码2.4 赛题实战 三、按键模块3.1 赛题要求3.2 模块原理图3.3 编写代码3.4 赛题实战 四、串口模块4.1 赛题要求4…系列文章目录 留空 文章目录 系列文章目录前言一、LED模块1.1 赛题要求1.2 模块原理图1.3 编写代码1.4 赛题实战 二、LCD模块2.1 赛题要求2.2 模块原理图2.3 编写代码2.4 赛题实战 三、按键模块3.1 赛题要求3.2 模块原理图3.3 编写代码3.4 赛题实战 四、串口模块4.1 赛题要求4.2 模块原理图4.3 编写代码4.4 赛题实战 五、PWM模块5.1 赛题要求5.2 模块原理图5.3 编写代码5.4 赛题实战 六、ADC模块6.1 赛题要求6.2 模块原理图6.3 编写代码6.4 赛题实战 七、EEPROM模块7.1 赛题要求7.2 模块原理图7.3 编写代码7.4 赛题实战 八、RTC模块8.1 赛题要求8.2 模块原理图8.3 编写代码8.4 赛题实战 九、补充总结 前言 自用 一、LED模块 1.1 赛题要求 1单独或几个 LED灯亮起/熄灭【不完整总结】 ​ LED灯LD1以0.5秒的频率闪烁。 ​ 上限提醒指示灯以0.2秒为间隔闪烁下限指示灯熄灭。 ​ 按下某个按键后可以启用或禁用LED指示灯功能LED指示灯功能禁用后所有指示灯处于熄灭状态。 ​ 指示灯LD2以0.1秒为间隔亮、灭闪烁报警5秒后熄灭。 ​ 指示灯LD2以0.1秒为间隔亮、灭闪烁报警5次后熄灭。 2LED流水指示 ​ 升降机上下行时4个LEDLD5-LD8组成流水灯用来表示升降机的运行方向。合理选择流水灯的流水方式和时间间隔。 1.2 模块原理图 我们可以把原理图分成两部分一部分为左边一列LED灯另一部分为SN74HC573ADWR锁存器和输出PC端。 左部分LED灯为低电平点亮初始化时设置为高电平熄灭。 右部分SN74HC573ADWR芯片U1这是一款八位透明锁存器具有三态输出缓冲区属于 74HC 系列 高速 CMOS 逻辑集成电路用于锁存数据。当输入在有效状态时通常是高电平这个锁存器能够实时地将输入数据传送到输出。当输入无效时它会保持最后传入的数据。 各个引脚功能如下表 引脚名称功能描述GND电源负极VCC电源正极输入 (D)数据输入引脚输出 (Q)数据输出引脚LE锁存控制引脚。高电平保存最新的输入数据但不输出低电平不锁存直接输出数据OE#输出使能引脚。低电平输出数据高电平停止输出高阻态 输入输出功能模式 OE# 就是芯片的开关OE#为低时芯片工作OE#为高时停止工作。蓝桥杯板子上的OE#接地无需设置。 LE引脚通俗易懂一点理解D值一直变化根据LE的状态输出Q值。 D1234567890LE1110011111Q1233367890 LE 1 时Q 直接输出 D。LE 0 时Q 保持上次的值不更新。LE 1 时Q 输出当前 D 的值。 这个在LED和LCD的冲突引脚设置中非常有用必须理解 PC8 - 15同时控制LED和LCD如果左侧锁存器Q直接输出 D就会导致更新LCD时出现LED乱闪现象。 为了解决LED与LCD之间的冲突我们需要合理利用锁存器的特性。我们跑代码时大部分时间在更新LCD页面在更新LED时的一瞬间打开锁存器将LED设置完成后关闭锁存器。 1.3 编写代码 1.3.1 分析代码不想看分析的直接看1.3.2 开始编写代码 首先我们一共有八个LED灯控制方法有两种一种是单独控制一种是全部控制。 假设现在要点亮LED1/3/5/7四个灯 第一种单独控制 HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOC,GPIO_PIN_10,GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOC,GPIO_PIN_12,GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOC,GPIO_PIN_14,GPIO_PIN_RESET);或 HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8 | GPIO_PIN_10 | GPIO_PIN_12 | GPIO_PIN_14, GPIO_PIN_RESET);单独控制太繁琐每次都要写一长串 第二种全部控制 HAL_GPIO_WritePin(GPIOC,0xFFFF,GPIO_PIN_RESET); //设置 选中为1的端口 为低电平0xFFFF – 转成二进制 – 1111 1111 1111 1111 – 对应PC端一共十六位PC0到PC15全部为低电平 PC15PC14PC13PC12…PC2PC1PC01111…111 再举个例子 HAL_GPIO_WritePin(GPIOC,0x0001,GPIO_PIN_RESET); //设置PC1为低电平0x0001 – 转成二进制 – 0000 0000 0000 0001 – 对应PC端只有PC1为1那么这句代码就只设置PC1为低电平其他引脚将保持原来的电平状态无论它们之前是高还是低电平。 那么我们现在点亮LED1/3/5/7四个灯也就是设置PC8、PC10、PC12和PC14为低电平。 0101 0101 0000 0000 – 转成十六进制 – 0x5500 PC15PC14PC13PC12PC11PC10PC9PC8…01010101… 代码如下 HAL_GPIO_WritePin(GPIOC,0x5500,GPIO_PIN_RESET); 因为因为因为我们只需要控制PC8到15所以我们只需要控制高八位不需要后面低八位。 那么简化一下0x5500 等同于 0x55 8 0x55 0x0055 0000 0000 0101 0101 -- 再左移八位 8 — 0101 0101 0000 0000 0x5500 改过的代码如下 HAL_GPIO_WritePin(GPIOC,0x55 8,GPIO_PIN_RESET); 1.3.2 完整代码 首先在BSP中创建两个文件LED.c和LED.h LED.h #ifndef __LED_H #define __LED_H#include main.hvoid LED_Disp(uint16_t ucLED);#endifLED.c #include LED.hvoid LED_Disp(uint16_t ucLED) {//先关闭所有灯再点亮指定灯HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOC,ucLED 8,GPIO_PIN_RESET);//关闭锁存(输出数据) 再开启锁存HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET); }先设置好数据再把锁存关闭关闭的一瞬间输出数据再立马开启锁存。 main.c /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include LED.h /* USER CODE END Includes */... 省略 .../* USER CODE BEGIN WHILE */while (1){LED_Disp(0x55);/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}1.4 赛题实战 1单独或多个灯长亮/灭 LED_Disp(0x01); // 假设为LED1亮LED_Disp(0x00); // 假设为LED1灭LED_Disp(0x15); // 假设为LED1、LED3、LED5亮2LED 灯以 0.1 秒频率闪烁五秒后关闭 void LED1_Blink_5s(void) {if(uwTick - led1_uwTick 5000) return; else{LED_Disp(0x01); // LED1 每 0.1 秒闪烁一次 HAL_Delay(100);LED_Disp(0x00); HAL_Delay(100); }if(led1_uwTick 0) led1_uwTick uwTick; }3LED 灯以 0.1 秒频率闪烁五次后关闭 void LED1_Blink_5times(void) { for (int i 0; i 5; i) // 循环 5 次 { LED_Disp(0x01); // LED1 每 0.1 秒闪烁一次 HAL_Delay(100);LED_Disp(0x00); HAL_Delay(100); } LED_Disp(0x00); // 5 次后熄灭 LED1 }4LED流水指示 /*** 简单版 ***/ void LED_Running(uint8_t mode) {if(mode 1){for(int i0;i 8;i){LED_Disp(0x01 i);HAL_Delay(50);} }else if(mode 2){for(int i7;i 0;i--){LED_Disp(0x01 i);HAL_Delay(50);} } }/*** 稍微复杂版 ***/ void LED_Running(uint8_t mode) {if(mode 1){i (i 1) % 8; LED_Disp(0x01 i);}else if(mode 2){i (i - 1 8) % 8;LED_Disp(0x01 i);} }二、LCD模块 2.1 赛题要求 1显示背景色(BackColor)黑色 2显示前景色(TextColor)白色 3严格按照图示要求设计各个信息项的名称区分字母大小写和行列位置。 注选手收到的嵌入式主板配套液晶屏驱动芯片控制器型号可能为ILI9328、ILI9325或uc8230数据包中提供的液晶驱动代码能够兼容ILI9328、ILI9325、uc8230三种类型的液晶控制器不需要任何改动。 2.2 模块原理图 这个是LCD的双排针如下图接下来我们看看每个引脚都有什么功能 原理图与ILI9325对照如下表 原理图引脚ILI9325 引脚功能描述LCD_CS#nCS片选信号低电平时启用ILI9325选择和访问LCDLCD_WR#nWR/SCL写使能信号低电平时启用写操作在SPI模式下为时钟信号LCD_RST#nRESET复位信号低电平初始化LCD启动时需要执行电源复位LCD_RSRS寄存器选择信号低电平选择索引或状态寄存器高电平选择控制寄存器LCD_RDnRD读使能信号低电平时启用读操作LCD_D0 - D15DB[17:0]数据总线用于并行数据传输在不同宽度的接口模式下使用LCD_HDR不直接对应VDDVCI/VDD电源引脚提供电源电压到LCD模块和控制器GNDGND地线电源用于连接系统地 要深入了解可以去查看数据手册这儿并不需要我们理解看看就好啦。 2.3 编写代码 底层代码不需要自己写当天会给赛点资源包里面就有完整的代码 第一步设置相关引脚。 在赛点资源包中main.c有相关引脚定义初始化。 /*** brief GPIO Initialization Function* param None* retval None*/ static void MX_GPIO_Init(void) {GPIO_InitTypeDef GPIO_InitStruct {0};/* GPIO Ports Clock Enable */__HAL_RCC_GPIOC_CLK_ENABLE();__HAL_RCC_GPIOF_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();/*Configure GPIO pin Output Level */HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15 | GPIO_PIN_0| GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4| GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8| GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12, GPIO_PIN_RESET);/*Configure GPIO pin Output Level */HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);/*Configure GPIO pin Output Level */HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5 | GPIO_PIN_8 | GPIO_PIN_9, GPIO_PIN_RESET);/*Configure GPIO pins : PC13 PC14 PC15 PC0PC1 PC2 PC3 PC4PC5 PC6 PC7 PC8PC9 PC10 PC11 PC12 */GPIO_InitStruct.Pin GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15 | GPIO_PIN_0| GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4| GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8| GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12;GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull GPIO_NOPULL;GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOC, GPIO_InitStruct);/*Configure GPIO pin : PA8 */GPIO_InitStruct.Pin GPIO_PIN_8;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);/*Configure GPIO pins : PB5 PB8 PB9 */GPIO_InitStruct.Pin GPIO_PIN_5 | GPIO_PIN_8 | GPIO_PIN_9;GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull GPIO_NOPULL;GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOB, GPIO_InitStruct); }我们可以直接复制到自己新创建的main.c中方法蓝桥杯嵌入式省赛国赛程序题手把手教程-电子爱好者老王 或者 在cubeMX中重新设置就不需要粘贴以上的代码之前在LED中有设置过一部分的引脚对照原理图或是赛点提供的底层代码把剩下的引脚设置成output模式其他默认如下 第二步把赛点资源包中lcd.c和lcd.h复制到自己的工程中。 还有一个fonts.h。 最后呢在赛点资源包main.c中复制主函数中的代码到自己工程中 int main(void) {/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();/* USER CODE BEGIN 2 */LCD_Init();HAL_Delay(100); /* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */LCD_SetBackColor(Black);LCD_SetTextColor(White);LCD_Clear(Black);HAL_Delay(100);LCD_DisplayStringLine(Line4, (unsigned char *) Hello,world. );HAL_Delay(1000); /* USER CODE BEGIN WHILE */while (1){ /* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */ }上电下载程序点亮 2.4 赛题实战 我们根据赛题要求练习一下改背景/字体颜色在指定行列中写信息。 第一行第七列开始Data单词。 LCD_DisplayStringLine(Line1, (unsigned char *) Data );//[空六格]Data[空]第三行第五列开始电压显示字母和数字。 #include stdio.hunsigned char LCD_Text[22]; float R37V 3.02;sprintf((char *)LCD_Text, V:%0.2fV ,R37V); LCD_DisplayStringLine(Line3,LCD_Text); 第五行第五列开始模式切换显示字母。 LCD_DisplayStringLine(Line5, (unsigned char *) Mode:AUTO );这节非常简单对吧 三、按键模块 3.1 赛题要求 1短按 2长按 3双击 4按键应进行有效的防抖处理避免出现一次按下、多次触发等情形。 3.2 模块原理图 按键按下PB1检测到低电平输入按键松开变回高电平。 3.3 编写代码 根据原理图设置引脚为input模式 首先在BSP 中创建KEY.c和KEY.h uint8_t KEY_Scan(void): 这个函数是用于扫描按键的状态。监测GPIO引脚并根据引脚的电平来判断按键是否被按下。如果某个引脚的电平为低0则返回对应的按键编号1到4。void KEY_Proc(void)这个函数是用于监测按键的按下和释放逻辑。 KEY.c #include KEY.h__IO uint32_t uwTick_Key1 0; uint16_t KEY_Val,KEY_Old,KEY_Up,KEY_Down;uint8_t KEY_Scan(void) {uint8_t KEY_Num 0;if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) 0) KEY_Num 1;if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1) 0) KEY_Num 2;if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2) 0) KEY_Num 3;if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) 0) KEY_Num 4;return KEY_Num; }void KEY_Proc(void) {if((uwTick - uwTick_Key1) 50) return; //这行代码的作用是防抖动。//如果上次按键扫描时间距离当前时间太短小于50毫秒就不进行扫描防止按键抖动。uwTick_Key1 uwTick;KEY_Val KEY_Scan();KEY_Down KEY_Val (KEY_Val ^ KEY_Old);KEY_Up ~KEY_Val (KEY_Val ^ KEY_Old);KEY_Old KEY_Val; }KEY.h #ifndef __KEY_H #define __KEY_H#include main.huint8_t KEY_Scan(void); void KEY_Proc(void);#endif3.3.1 短按 main.c中直接调用 #include KEY.h... ...while (1){KEY_Proc();if(KEY_Val 1){LED_Disp(0x01);}else if(KEY_Val 2){LED_Disp(0x00);} } }按下按键1则LED1亮按下按键2则LED2亮。 3.3.2 长按 在KEY.c基础上增加函数还有KEY.h中 uint8_t KEY_State(void) {uint8_t result 0; // 初始化结果变量用于保存返回的按键状态// 检查是否有按键按下if(KEY_Down){// 如果有按键按下记录当前时间戳uwTickuwTick_Key2 uwTick;}// 判断按键按下时间是否在800ms以内if(uwTick - uwTick_Key2 800){// 如果按下时间小于等于800ms表示是短按switch(KEY_Up) // 检查哪个按键释放{case 1: result 1; break; // 按键1短按case 2: result 2; break; // 按键2短按case 3: result 3; break; // 按键3短按case 4: result 4; break; // 按键4短按}}// 如果按键按下时间超过了800mselse if(uwTick - uwTick_Key2 800){// 如果按键按下超过800ms表示是长按switch(KEY_Val) // 检查哪个按键被长按{case 1: result 5; break; // 按键1长按case 2: result 6; break; // 按键2长按case 3: result 7; break; // 按键3长按case 4: result 8; break; // 按键4长按}}return result; // 返回按键状态短按或长按 }main.c中调用 while (1){KEY_Proc();if(KEY_State() 1){LED_Disp(0x01);}else if(KEY_State() 11){LED_Disp(0x00);}}3.3.3 双击 还有点问题略 3.4 赛题实战 参考上面的代码根据逻辑编写。 void KEY_STATE(void) {switch(KEY_State()){case 1:// 对应的功能break;case 2:// 对应的功能break;case 3:// 对应的功能break;... ...case 8:// 对应的功能break;} }四、串口模块 4.1 赛题要求 1USART1通信波特率9600bps。 2电脑端发送–单片机接收命令单片机发送–电脑端接收命令。 3识别出通过串口接收到的指令存在格式或逻辑错误。 4.2 模块原理图 串口Universal Asynchronous Receiver/Transmitter通用异步收发传输器是一种常见的通信接口用于设备之间的数据传输。它通过两根信号线TXD和RXD实现全双工通信 TXD发送数据线Transmit Data用于发送数据。RXD接收数据线Receive Data用于接收数据。 在这我们只需要懂设置就好啦想深入了解可以看看江协的视频 4.3 编写代码 第一步打开CubeMX。 配置串口UART 找到 Connectivity - USART1。将 Mode 设置为 Asynchronous异步模式。设置以下参数 Baud Rate波特率9600Word Length数据位长度默认 8 位。Parity校验位默认 None。Stop Bits停止位默认 1 位。Data DirectionReceive and Transmit。 配置中断 如果使用中断接收数据 在 NVIC Settings 中勾选 USART1 global interrupt。设置优先级看情况。 生成代码 4.3.1 发送数据 简简单单一个函数 HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout)通过 UART 发送数据。 小栗子奉上 char Tx_Data[30] 你好\r\n; // 定义并初始化数组HAL_UART_Transmit(huart1, (uint8_t *)Tx_Data, strlen(Tx_Data), 50); // 发送数据注意注意注意 使用 strlen(Tx_Data)和 sizeof(Tx_Data)是不一样的 sizeof 是一个运算符用于计算变量或数据类型所占用的内存大小以字节为单位。 对于字符串sizeof 会计算整个字符数组的大小包括末尾的空字符 \0。 char str[] Hello; size_t size sizeof(str); // size 6字符串 Hello 占用 6 个字节5 个字符 1 个 \0。 strlen 是一个函数用于计算字符串的实际长度不包括末尾的空字符 \0。 char str[] Hello; size_t len strlen(str); // len 5字符串 Hello 的长度是 5。 如果我们使用sizeof就需要在后面 -1。很容易忘记不推荐 HAL_UART_Transmit(huart1, (uint8_t *)Tx_Data,sizeof(Tx_Data) - 1, 50); // 需要 - 14.3.1 接收数据 首先确定CubeMX中配置好了中断。 简简单单两个函数 HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)启动 UART 接收中断模式。HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) UART 接收完成回调函数。 小栗子奉上 char Rx_Data[30]; unsigned char rx_data,rx_num;int main(void) {HAL_UART_Receive_IT(huart1,(uint8_t *)rx_data,1);while (1){sprintf((char *)LCD_Text, time:%s,car_time);LCD_DisplayStringLine(Line7,LCD_Text); } }void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart huart1) // 判断是否是 huart1 触发的中断{ Rx_Data[rx_num] rx_data; // 将接收到的数据存储到 Rx_Data 数组中并递增 rx_numHAL_UART_Receive_IT(huart1, rx_data, 1); // 重新启动接收中断准备接收下一个字节} }4.4 赛题实战 根据题目要求需要串口接收和发送以下内容 接收车辆入场信息 格式停车类型:车辆编号:进入时间 例如CNBR:A392:200202120000 停车类型是CNBR可能是普通停车场车辆编号是A392进入时间是2020年2月2日12:00:00。 接收车辆出场信息 格式停车类型:车辆编号:退出时间 例如VNBR:D583:200202213205 停车类型是VNBR可能是VIP停车场车辆编号是D583退出时间是2020年2月2日13:25:05。 输出计费信息 格式停车类型:车辆编号:停车时长:费用 例如VNBR:D583:10:20.00 停车类型是VNBRVIP停车场车辆编号是D583停车时长为10小时停车费用为20.00元。 上面这些太复杂到写真题时在具体写。这里只写简单的逻辑电脑端从串口发送车辆信息到单片机单片机对信息进行分析最后单片机从串口发送信息到电脑端。 例如电脑端发送CNBR:A392:200202120000单片机接收后返回时间信息20-02-02-12。 下面写一下实现的代码 char Rx_Data[30]; char Tx_Data[30]; char car_type[5],car_num[5],car_time[13]; int year,month,day,min,s;unsigned char LCD_Text[30]; unsigned char rx_data,rx_num;void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); void Usart_Tx(void); void Usart_Rx(void);int main(void) {HAL_UART_Receive_IT(huart1,(uint8_t *)rx_data,1);while (1){KEY_Proc();if(KEY_State() 1){Usart_Tx();}Usart_Rx();} }/******************** 发送串口信息 *********************/ void Usart_Tx(void) {//方法1HAL_UART_Transmit(huart1,(uint8_t *)CNBR:A392:200202120000,22,50);//方法2 // sprintf(Tx_Data,CNBR:A392:200202120000); // HAL_UART_Transmit(huart1,(uint8_t *)Tx_Data,strlen(Tx_Data),50); }/******************** 中断接收串口 *********************/ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart huart1) // 判断是否是 huart1 触发的中断{ Rx_Data[rx_num] rx_data; // 将接收到的数据存储到 Rx_Data 数组中并递增 rx_numHAL_UART_Receive_IT(huart1, rx_data, 1); // 重新启动接收中断准备接收下一个字节} }/******************** 处理串口信息 *********************/ void Usart_Rx(void) {if(rx_num 22 Rx_Data[4] : Rx_Data[9] :){//处理接收信息sscanf(Rx_Data,%4s:%4s:%12s,car_type,car_num,car_time); sprintf((char *)LCD_Text, type:%s,car_type);LCD_DisplayStringLine(Line5,LCD_Text); sprintf((char *)LCD_Text, num:%s,car_num);LCD_DisplayStringLine(Line6,LCD_Text); sprintf((char *)LCD_Text, time:%s,car_time);LCD_DisplayStringLine(Line7,LCD_Text); //处理时间信息sscanf(car_time,%2d%2d%2d%2d%2d,year,month,day,min,s); sprintf(Tx_Data,%d-%d-%d-%d,year,month,day,min); HAL_UART_Transmit(huart1,(uint8_t *)Tx_Data,30,50);rx_num 0; } }这儿我们需要理解一下两个函数的使用。sscanf 和 sprintf C 标准库中用于格式化输入和输出的函数。 sscanf 用于从字符串中提取数据。 sscanf(car_time,%2d%2d%2d%2d%2d,year,month,day,min,s); 格式化字符串%2d%2d%2d%2d%2d 表示提取car_time中5 个 2 位整数。 提取的数据存储到变量 year, month, day, min, s 中。 sprintf 用于将数据格式化并写入字符串。 sprintf(Tx_Data,%d-%d-%d-%d,year,month,day,min); 格式化字符串 “%d-%d-%d-%d” 表示输出 4 个整数year,month,day,min并用连字符分隔。 格式化后的数据存储到 Tx_Data 中。 然后打开我们赛点资源包中的Tool找到STC-ISP软件 五、PWM模块 5.1 赛题要求 PWM输出 1使用PA6PA7输出频率固定为100Hz占空比可调节的脉冲信号。 2使用PA6PA7输出PWM信号频率1KHz占空比为60%。 PWM输入捕获 1测量PA15PB4引脚接入的脉冲信号。 2测量频率数据频率数据单位为Hz数据保留整数位。 5.2 模块原理图 XL555 是一款经典的 定时器集成电路广泛应用于电子电路中用于生成精确的定时信号、方波、脉冲或PWM信号。通过滑动变阻器 R40、固定电阻 R32100Ω 和电容 C2010nF 生成可调频率的PWM信号。原理太多直接省略啦 微控制器的 PA15 和 PB4 引脚用于监测信号。 设置输出PWM的原理 1定时器计数器CNT 定时器的计数器CNT从0开始递增直到达到自动重载寄存器ARR的值然后重新从0开始计数。计数器的递增频率由定时器的时钟源和预分频器PSC决定。 2自动重载寄存器ARR ARR决定了PWM信号的周期。当计数器CNT达到ARR的值时计数器会重置为0同时PWM信号的一个周期结束。公式PWM周期 系统时钟 / (PSC 1) / (ARR 1) 3捕获比较寄存器CCR CCR决定了PWM信号的占空比。当计数器CNT的值等于CCR的值时PWM信号的输出电平会发生变化例如从高电平变为低电平。公式占空比 (CCR / (ARR 1)) × 100% 捕获PWM的原理 1频率测量 通过捕获两个上升沿或下降沿之间的时间差可以计算出PWM信号的周期从而得到频率。公式频率 1 / 周期。 2占空比测量 通过捕获一个周期内高电平的时间即上升沿到下降沿的时间可以计算出占空比。公式占空比 (高电平时间 / 周期) × 100%。 5.3 编写代码 5.3.1 输出PWM 第一步打开CubeMX。 假设我们现在使用PA6对应就是TIM3的通道1输出PWM信号。 启用定时器并配置PWM模式 选择定时器 → 设置PSC、ARR、CCR → 生成代码。 示例配置生成1kHz50%占空比的PWM 熟记以下公式 PWM频率 Freq 系统时钟 / (PSC 1) / (ARR 1)占空比 Duty (CCR / (ARR 1)) × 100% PSC预分频器、ARR自动重载值、CCR捕获/比较寄存器。我们的系统时钟时钟源是80MHz就是80后边6个0。 我们设置的PSC 799ARR 99CCR 50这个在代码里设置这里假设50 Freq 80 000000 / (799 1) / (99 1) 80 000000 / 800 / 100 1000Duty (CCR / (ARR 1)) × 100% 50 / (99 1) × 100% 50%。 第二步记住输出PWM函数 以下是比赛编写输出PWM代码时用到的函数 HAL_TIM_PWM_Start: 启动PWM输出。HAL_TIM_PWM_Stop: 停止PWM输出。**__HAL_TIM_SetCompare(HANDLE, CHANNEL, COMPARE) **设置 PWM 占空比。 下面是一个简单的例子 int main(void) {... ...HAL_TIM_PWM_Start(htim3,TIM_CHANNEL_1); //启动指定定时器通道的PWM输出。__HAL_TIM_SetCompare(htim3, TIM_CHANNEL_1, 50); //设置PWM的占空比50%。while (1){... ...} }5.3.2 捕获PWM 第一步打开CubeMX。 假设我们现在使用PA15对应就是TIM8的通道输入捕获R40PWM信号。 配置输入捕获模式 选择定时器找到 TIM8设置时钟源为 Internal Clock。配置通道 TIM2 Channel 1 → Input Capture direct mode。 参数设置 Prescaler (PSC)71分频完为1MHz。Counter Period (ARR)6553516位定时器最大值防止溢出。 触发边沿 IC1 Polarity → Rising Edge捕获上升沿。 启用中断 在 NVIC Settings 中勾选 TIM2 global interrupt。生成代码后中断处理函数会自动生成。 还是刚刚那些公式 PWM频率 Freq 系统时钟 / (PSC 1) / (ARR 1)占空比 Duty (CCR / (ARR 1)) × 100% 这次我们是已知PSC但是需要我们实时监测ARR和CCR的值 我们设置的PSC 79 Freq 80 000000 / (79 1) / (ARR 1)Duty (CCR / (ARR 1)) × 100%。 第二步记住输入捕获PWM函数 以下是比赛编写输出PWM代码时用到的函数 HAL_TIM_IC_Start_IT启动某个定时器的某个通道的输入捕获功能并且使能中断。HAL_TIM_IC_Stop_IT: 停止某个定时器的某个通道的输入捕获功能。HAL_TIM_ReadCapturedValue(const TIM_HandleTypeDef *htim, uint32_t Channel)读取某个定时器的某个通道捕获到的值。__HAL_TIM_SET_CAPTUREPOLARITY(HANDLE, CHANNEL, POLARITY)设置某个定时器的某个通道的捕获极性为上升沿或下降沿捕获。HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)这是一个回调函数当定时器捕获到信号时上升沿或下降沿系统会自动调用这个函数。 下面是一个简单的例子 uint32_t R40_Freq, R40_Duty; // 用于存储频率和占空比 uint32_t CCR1, CCR2, CCR3, High_CCR, Low_CCR, Period_CCR; // 用于存储捕获值和计算周期 uint8_t Capture_Flag 1Capture_end 0; // 捕获标志用于切换捕获状态... ...int main(void) {... ...HAL_TIM_IC_Start_IT(htim8, TIM_CHANNEL_1); //启动定时器8通道1的输入捕获并启用中断while (1){Get_Freq(); // 在主循环中调用频率计算函数} }void Get_Freq(void) { if (Capture_end 0) return; // 如果捕获未完成直接返回if (CCR1 CCR3) Period_CCR CCR3 - CCR1; // 计算周期正常情况else Period_CCR 0xFFFF 1 - CCR1 CCR3; // 处理计数器溢出的情况 R40_Freq 80000000 / 80 / Period_CCR; // 计算频率假设系统时钟为80MHz预分频为80sprintf((char *)LCD_Test, R40_Freq %dHz , R40_Freq); // 格式化频率值LCD_DisplayStringLine(Line5, LCD_Test); // 在LCD上显示频率值Capture_end 0; // 重置捕获完成标志 }void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {if (htim htim8) // 判断是否是定时器8触发的中断{switch (Capture_Flag) // 根据捕获标志切换捕获状态{case 1:CCR1 HAL_TIM_ReadCapturedValue(htim8, TIM_CHANNEL_1); //读取第一次捕获值Capture_Flag 2; //切换到下一次捕获状态break;case 2:CCR2 HAL_TIM_ReadCapturedValue(htim8, TIM_CHANNEL_1); //读取第二次捕获值Capture_Flag 1; //切换回下一次捕获状态Capture_end 1; //捕获完成标志break; default:break;}HAL_TIM_IC_Start_IT(htim8, TIM_CHANNEL_1); // 重新启动输入捕获} }5.4 赛题实战 5.4.1 输出PWM uint16_t PA6_Duty;void Get_Duty(void);int main(void) {HAL_TIM_PWM_Start(htim3,TIM_CHANNEL_1);while (1){KEY_Proc();Set_Duty();} }void Set_Duty(void) {if(KEY_State() 1) // 如果按键1被按下{PA6_Duty 10; // 增加PA6_Duty的值每次增加10if(PA6_Duty 90) PA6_Duty 0; // 如果PA6_Duty大于或等于90则将其重置为0}// 设置TIM3的通道1的占空比__HAL_TIM_SetCompare(htim3, TIM_CHANNEL_1, PA6_Duty);// 读取TIM3的通道1的当前占空比并将其赋值给PA6_DutyPA6_Duty __HAL_TIM_GetCompare(htim3, TIM_CHANNEL_1);// LCD显示sprintf((char *)LCD_Test, PA6_Duty %d, PA6_Duty);LCD_DisplayStringLine(Line7, LCD_Test); }5.4.2 捕获PWM uint32_t R40_Freq, R40_Duty;//用于存储频率和占空比 uint32_t CCR1, CCR2, CCR3, High_CCR, Low_CCR, Period_CCR;//用于存储捕获值和计算周期 uint8_t Capture_Flag 1, Capture_end 0;//Flag用于切换捕获状态end表示捕获完成标志void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim); //定时器输入捕获回调函数声明 void Get_Duty(void); // 计算占空比函数声明 void Get_Freq(void); // 计算频率函数声明int main(void) {HAL_TIM_IC_Start_IT(htim8, TIM_CHANNEL_1); //启动定时器8通道1的输入捕获并启用中断while (1){//Get_Freq(); // 在主循环中调用频率计算函数Get_Duty(); // 在主循环中调用占空比计算函数} }/***************** 计算PWM占空比和频率 *****************/ void Get_Freq(void) { if (Capture_end 0) return; // 如果捕获未完成直接返回if (CCR1 CCR3) Period_CCR CCR3 - CCR1; // 计算周期正常情况else Period_CCR 0xFFFF 1 - CCR1 CCR3; // 处理计数器溢出的情况 R40_Freq 80000000 / 80 / Period_CCR; // 计算频率系统时钟为80MHz预分频为80sprintf((char *)LCD_Test, R40_Freq %dHz , R40_Freq); LCD_DisplayStringLine(Line5, LCD_Test); Capture_end 0; // 重置捕获完成标志 }void Get_Duty(void) {if (Capture_end 0) return; // 如果捕获未完成直接返回if (CCR1 CCR2) High_CCR CCR2 - CCR1; // 计算高电平时间正常情况else High_CCR 0xFFFF 1 - CCR1 CCR2; // 处理计数器溢出的情况if (CCR1 CCR3) Period_CCR CCR3 - CCR1; // 计算周期正常情况else Period_CCR 0xFFFF 1 - CCR1 CCR3; // 处理计数器溢出的情况 R40_Freq 80000000 / 80 / Period_CCR; // 计算频率系统时钟为80MHz预分频为80R40_Duty High_CCR * 100 / (Period_CCR 1); // 计算占空比高电平时间占周期的百分比sprintf((char *)LCD_Test, R40_Freq %dHz , R40_Freq); LCD_DisplayStringLine(Line6, LCD_Test); sprintf((char *)LCD_Test, R40_Duty %d%% , R40_Duty); LCD_DisplayStringLine(Line7, LCD_Test); Capture_end 0; // 重置捕获完成标志 }void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {if (htim htim8) // 判断是否是定时器8触发的中断{switch (Capture_Flag) // 根据捕获标志切换捕获状态{case 1:CCR1 HAL_TIM_ReadCapturedValue(htim8, TIM_CHANNEL_1);//第一次捕获值上升沿__HAL_TIM_SET_CAPTUREPOLARITY(htim8,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_FALLING);//设置为下降沿捕获Capture_Flag 2; // 切换到下一次捕获状态break;case 2:CCR2 HAL_TIM_ReadCapturedValue(htim8, TIM_CHANNEL_1);//第二次捕获值下降沿__HAL_TIM_SET_CAPTUREPOLARITY(htim8,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_RISING);//设置为上升沿捕获Capture_Flag 3; // 切换到下一次捕获状态break; case 3:CCR3 HAL_TIM_ReadCapturedValue(htim8, TIM_CHANNEL_1);//第三次捕获值上升沿Capture_end 1; // 设置捕获完成标志Capture_Flag 1; // 重置捕获状态break; default:break;}HAL_TIM_IC_Start_IT(htim8, TIM_CHANNEL_1);//重新启动输入捕获} }六、ADC模块 6.1 赛题要求 1根据试题要求设计合理的电压数据采样频率并对ADC采样到的电压数据进行有效的数字滤波。 2使用竞赛平台微控制器内部ADC测量电位器R37R38输出的电压信号。 6.2 模块原理图 滑动变阻器也称为电位器接在 VDD 和 GND 之间并将滑动端连接到单片机的 ADC 引脚这是一种常见的模拟信号采集电路。 非常简单主要就是理解一下分压不理解也没啥记下就OK 工作原理 滑动变阻器的作用 滑动变阻器是一个可调电阻其总电阻值是固定的10kΩ。滑动端将变阻器分为两部分电阻R1 和 R2。当滑动端移动时R1 和 R2 的比例会发生变化但 R1 R2 的总电阻不变。 分压原理 滑动变阻器与电源VDD 和 GND形成一个分压电路。滑动端的电压由 R1 和 R2 的比例决定Vadc VDD * R2 / (R1 R2)当滑动端移动时R1 和 R2 的比例变化导致 Vadc 的电压变化。 ADC 的作用 单片机的 ADC模数转换器将滑动端的模拟电压V_ADC转换为数字值。ADC 的分辨率决定了数字值的精度例如 12 位 ADC 的分辨率为 0~4095。 6.3 编写代码 第一步打开CubeMX。 以R37为例对应的是PB15也就是ADC2_IN15。 选择IN15 Single-ended单通道然后把Sampling Time采样时间设置到最大。 为什么设置最大采样时间捏 信号较弱或阻抗高如果信号很弱比如声音很小或者信号源阻抗很高比如信号线很长ADC需要更多时间才能“听清楚”信号。减少噪声干扰如果信号中有噪声比如杂音较长的采样时间可以让ADC“过滤”掉这些噪声得到更准确的值。提高精度采样时间越长ADC采集的信号越稳定结果越精确。 设置最大采样时间就像让ADC“多听一会儿”信号这样可以听清楚弱信号过滤掉噪音得到更准确的结果。 还有一个需要理解的就是ResolutionADC分辨率。 默认是ADC 12-bit resolutionADC分辨率为12位。我们不需要改动。 分辨率ADC将模拟信号转换为数字信号时能够区分的电压级别的数量。 就像尺子一样有毫米级有厘米级。分辨率越高ADC能够区分的电压级别越多测量精度越高。 12位分辨率ADC可以输出 2^124096 个不同的数字值范围是0到4095。 12位ADC可以将参考电压如3.3V分为4096个级别每个级别对应的电压间隔为0.8mV。 第二步记住启动和获取函数 启动ADC采样 HAL_ADC_Start(hadc2);启动ADC模块hadc2开始一次新的采样。ADC开始采集输入信号如R37的电压并将其转换为数字值。 获取ADC转换值 R37_Num HAL_ADC_GetValue(hadc2);从ADC模块hadc2获取转换后的数字值并存储到变量 R37_Num 中。将模拟信号如R37的电压转换为数字信号。 ADC是自动将模拟电压转换为数字值所以我们读取到的就是一个数字 完整的如下 uint16_t R37_Num 0; // 存储ADC读取的原始值 float R37_Volt 0; // 存储转换后的电压值 __IO uint32_t uwTick_ADC_Point 0; // 记录上次读取ADC的Time Tick和防止频繁读取 void Get_R37V(void) { if (uwTick - uwTick_ADC_Point 50) return; // 如果小于50ms则直接返回避免频繁读取 uwTick_ADC_Point uwTick; // 更新上次读取时间戳 HAL_ADC_Start(hadc2); // 启动ADC以进行新一次的采样 R37_Num HAL_ADC_GetValue(hadc2); // 获取ADC转换值并存储到 R37_Num // 将ADC数字值转换为对应的电压值 R37_Volt R37_Num * 3.3 / 4095; } 将数字值转换为实际电压的公式是输入电压 数字值 × 参考电压 / 最大数字值 即R37_Volt R37_Num × (3.3 / 4095) 最后在循环中调用 while (1){Get_R37V();sprintf((char *)LCD_Test, R37_Volt %0.2fV ,R37_Volt);LCD_DisplayStringLine(Line4,LCD_Test); }完成了嘻嘻。 6.4 赛题实战 同6.3但是真题还要求“滤波”。 uint16_t R37_Num 0; // 存储ADC读取的原始值 float R37_Volt 0; // 存储转换后的电压值 __IO uint32_t uwTick_ADC_Point 0; // 记录上次读取ADC的Time Tick和防止频繁读取 void Get_R37V(void) { if (uwTick - uwTick_ADC_Point 50) return; // 如果小于50ms则直接返回避免频繁读取 uwTick_ADC_Point uwTick; // 更新上次读取时间戳 HAL_ADC_Start(hadc2); // 启动ADC以进行新一次的采样 for(int i1; i10;i){R37_Num HAL_ADC_GetValue(hadc2);HAL_Delay(1);}// 将ADC数字值转换为对应的电压值 R37_Volt R37_Num * 3.3 / 4095 / 10; R37_Num 0; }七、EEPROM模块 7.1 赛题要求 1设定好的定时时间存储在EEPROM中。小时分钟秒共三个整数 2设备重新上电时应从E2PROM中载入上、下限提醒指示灯、上限电压、 下限电压参数。整数/小数 3通过E2PROM完成商品库存数量以及商品单价的存储。整数/小数 4通过竞赛平台上的E2PROMAT24C02保存商品库存数量和价格信息。整数/小数 ​ 存储位置要求如下商品X库存数量存储地址E2PROM内部地址0。 7.2 模块原理图 在原理图中有两个芯片。 M24C02-WMNSTPEEPROM用于存储数据通过I2C通信。MCP4017-104ELT数字电位器用于调节电阻值通过I2C通信。 此部分我们只看存储模块。 M24C02-WMNSTPEEPROM 功能 电可擦除可编程只读存储器用于存储小量数据如配置参数、校准数据等。容量2Kbit256字节。 引脚说明 VCC (8)电源引脚接正电压通常3.3V或5V。GND (4)接地引脚。SDA (5)I2C数据线用于数据传输。SCL (6)I2C时钟线用于同步通信。WC# (7)写保护引脚低电平时允许写入高电平时禁止写入。E1, E2, E3 (1, 2, 3)地址引脚都是低电平用于设置设备地址。记住这个后面会用到 既然我们用IIC通信那我们就要了解单片机和AT24C02通信读取和写入的步骤 首先打开赛点资源包中的芯片手册。 1AT24C02的地址 设备地址如果有多个设备用于在I²C总线上唯一标识一个EEPROM设备。 地址格式 固定部分前4位固定为1010。可配置部分由A₂、A₁、A₀引脚或页地址位P0、P1、P2决定。读写位R/W最后1位0写1读。 这里有四种我们是第一种 型号地址格式说明1K/2K1 0 1 0 A₂ A₁ A₀ R/WA₂、A₁、A₀由硬件引脚决定支持8个设备地址。 我们的三个引脚都接了地E1/2/3那么我们读写的地址就是 A₂、A₁、A₀接地GND即A₂0、A₁0、A₀0。设备地址 写操作1010 000 0 → 0xA0十六进制。读操作1010 000 1 → 0xA1十六进制。 2字节写入 根据上图8以下是字节写入的步骤 1. 开始信号START – 2. 发送设备地址[写]DEVICE ADDRESS WRITE – 3. 等待应答ACK – 4. 发送内存地址WORD ADDRESS – 5. 等待应答ACK – 6. 写入需要存储的数据DATA – 7.等待应答ACK – 8. 停止信号STOP 3随机读取 根据图11以下是随机读取读指定地址的步骤如下 1. 开始信号START – 2. 发送设备地址[写]DEVICE ADDRESS WRITE – 3. 等待应答ACK – 4. 发送要读取的内存地址WORD ADDRESS – 5. 等待应答ACK – 6. 重复起始条件REPEATED START-- 7. 发送设备地[读]DEVICE ADDRESS READ – 8. 等待应答ACK – 9. 读取数据DATA-- 10. 无应答信号NOACK – 10. 停止信号STOP 7.3 编写代码 第一步将赛点资源包中i2c.c和i2c.h加入我们自己的工程中。 在i2c.h我们可以看到官方已经给了我们底层代码只需要理解就可以看着手册写啦 #ifndef __I2C_HAL_H #define __I2C_HAL_H#include stm32g4xx_hal.h void I2CStart(void); // 启动I2C通信发送起始条件 void I2CStop(void); // 停止I2C通信发送停止条件 unsigned char I2CWaitAck(void); // 等待从设备的应答信号ACK/NACK void I2CSendAck(void); // 主设备发送应答信号ACK void I2CSendNotAck(void); // 主设备发送非应答信号NACK void I2CSendByte(unsigned char cSendByte);// 主设备发送一个字节的数据 unsigned char I2CReceiveByte(void); // 主设备接收一个字节的数据 void I2CInit(void); // 初始化I2C外设#endif第二步根据手册进行编写下面注释对应刚刚上面的步骤 1. 写数据到EEPROM void EEPROM_Write(uint8_t WriteAdd, uint8_t Data) {I2CStart(); // 1. 开始信号STARTI2CSendByte(AT24C02_WriteAdd); // 2. 发送设备地址[写]DEVICE ADDRESS WRITEI2CWaitAck(); // 3. 等待应答ACKI2CSendByte(WriteAdd); // 4. 发送内存地址WORD ADDRESSI2CWaitAck(); // 5. 等待应答ACKI2CSendByte(Data); // 6. 写入需要存储的数据DATAI2CWaitAck(); // 7. 等待应答ACKI2CStop(); // 8. 停止信号STOPHAL_Delay(5); // 延时5ms确保写入完成 }2. 从EEPROM读取数据 uint8_t EEPROM_Read(uint8_t ReadAdd) {uint8_t Data;I2CStart(); // 1. 开始信号STARTI2CSendByte(AT24C02_WriteAdd); // 2. 发送设备地址[写]DEVICE ADDRESS WRITEI2CWaitAck(); // 3. 等待应答ACKI2CSendByte(ReadAdd); // 4. 发送要读取的内存地址WORD ADDRESSI2CWaitAck(); // 5. 等待应答ACKI2CStart(); // 6. 重复起始条件REPEATED STARTI2CSendByte(AT24C02_ReadAdd); // 7. 发送设备地址[读]DEVICE ADDRESS READI2CWaitAck(); // 8. 等待应答ACKData I2CReceiveByte(); // 9. 读取数据DATAI2CSendNotAck(); // 10. 无应答信号NOACKI2CStop(); // 11. 停止信号STOPreturn Data; // 返回读取的数据 }然后学会调用。 uint8_t data 0xAB; // 要写入的数据 uint8_t eeprom_Address 0x01; // 写入EEPROM的指定内存地址 uint8_t dataRead;// 写入数据到 EEPROM EEPROM_Write(eeprom_Address, data);// 从 EEPROM 读取数据 dataRead EEPROM_Read(eeprom_Address);7.4 赛题实战 真题中只有读取小数和整数。整数正常读取小数我把它变成整数写入读取时再转换回来。 /*************** EEPROM相关变量 **********************/ uint8_t EEP_Add[15] {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F}; // EEPROM存储地址 uint8_t Hour[5]{1,3,5,7,10},Min[5]{2,4,6,8,45},Sec[5]{1,2,4,45,59}; // 存储时间数据 uint8_t H[5] {0,0,0,0,0},M[5] {0,0,0,0,0},S[5] {0,0,0,0,0}; // 读取时间数据 uint8_t Shop_X 23,Shop_Y 10; // 存储商品个数 float Price_X 1.0,Price_Y 2.0; // 存储商品价格 uint8_t S_X 0,S_Y 0; // 读取的商品个数 float P_X 0,P_Y 0; // 读取的商品价格/***************** AT24C02存储和读取数据 *****************/// 存储商品数据到EEPROM void EEPROM_WriteShop(void) {EEPROM_Write(0x01,Shop_X); // 将Shop_X写入EEPROM地址0x01EEPROM_Write(0x02,Shop_Y); // 将Shop_Y写入EEPROM地址0x02EEPROM_Write(0x03,Price_X * 10); // 将Price_X放大10倍后写入EEPROM地址0x03EEPROM_Write(0x04,Price_Y * 10); // 将Price_Y放大10倍后写入EEPROM地址0x04LCD_DisplayStringLine(Line3,(unsigned char*) Write );sprintf((char *)LCD_Test,X:%d(%0.1f),Y:%d(%0.1f),Shop_X,Price_X,Shop_Y,Price_Y);LCD_DisplayStringLine(Line4,LCD_Test); }// 从EEPROM读取商品数据 void EEPROM_ReadShop(void) {S_X EEPROM_Read(0x01); // 从EEPROM地址0x01读取商品X的个数S_Y EEPROM_Read(0x02); // 从EEPROM地址0x02读取商品Y的个数P_X (float)EEPROM_Read(0x03) / 10; // 从EEPROM地址0x03读取商品X的价格并缩小10倍P_Y (float)EEPROM_Read(0x04) / 10; // 从EEPROM地址0x04读取商品Y的价格并缩小10倍LCD_DisplayStringLine(Line6,(unsigned char*) Read );sprintf((char *)LCD_Test,X:%d(%0.1f),Y:%d(%0.1f),S_X,P_X,S_Y,P_Y);LCD_DisplayStringLine(Line7,LCD_Test); }// 存储时间数据到EEPROM void EEPROM_WriteTime(void) {for(int i 0;i 5;i ) // 遍历5组时间数据{EEPROM_Write(EEP_Add[i],Hour[i]); // 将Hour[i]写入EEPROM地址EEP_Add[i]EEPROM_Write(EEP_Add[i5],Min[i]); // 将Min[i]写入EEPROM地址EEP_Add[i5]EEPROM_Write(EEP_Add[i10],Sec[i]); // 将Sec[i]写入EEPROM地址EEP_Add[i10]}LCD_DisplayStringLine(Line3,(unsigned char*) Write );sprintf((char *)LCD_Test,Time:%02d-%02d-%02d ,Hour[1],Min[1],Sec[1]);LCD_DisplayStringLine(Line4,LCD_Test); sprintf((char *)LCD_Test,Time:%02d-%02d-%02d ,Hour[4],Min[4],Sec[4]);LCD_DisplayStringLine(Line5,LCD_Test); }// 从EEPROM读取时间数据 void EEPROM_ReadTime(void) {for(int i 0;i 5;i ) // 遍历5组时间数据{H[i] EEPROM_Read(EEP_Add[i]); // 从EEPROM地址EEP_Add[i]读取Hour[i]M[i] EEPROM_Read(EEP_Add[i5]); // 从EEPROM地址EEP_Add[i5]读取Min[i]S[i] EEPROM_Read(EEP_Add[i10]); // 从EEPROM地址EEP_Add[i10]读取Sec[i]}LCD_DisplayStringLine(Line6,(unsigned char*) Read );sprintf((char *)LCD_Test,Time:%02d-%02d-%02d ,H[1],M[1],S[1]);LCD_DisplayStringLine(Line7,LCD_Test); sprintf((char *)LCD_Test,Time:%02d-%02d-%02d ,H[4],M[4],S[4]);LCD_DisplayStringLine(Line8,LCD_Test); }然后在循环调用就好啦 while (1){KEY_Proc();if(KEY_State() 1){ // EEPROM_WriteShop();EEPROM_WriteTime();}else if(KEY_State() 2){ // EEPROM_ReadShop();EEPROM_ReadTime();}}按下按键1将数据写入EEPROM并在LCD上显示写入状态和数据。按下按键2从EEPROM读取数据并在LCD上显示读取状态和数据。 特意放在最后最最最最最重要也特别容易忘的 要在int main中初始化IIC哦 int main(void) {I2CInit(); //IIC初始化while (1){... ...} }又完成了一个耶耶耶 八、RTC模块 8.1 赛题要求 1通过单片机片内RTC设计实现时钟功能。 8.2 模块原理图 RTCReal-Time Clock是STM32中独立于主系统的低功耗定时器用于实现日历、闹钟、周期性唤醒等功能。 在CubeMX中我们可以看到 RTC需要一个稳定的时钟源来计时我们有以下三种选择 HSE外部高速时钟通常为8MHz或更高频率可以通过分频器降低到32.768kHz。LSE外部低速晶振32.768kHz精度高常用于RTC。LSI内部低速RC振荡器约32kHz精度较低但无需外部晶振。 在很多板子上会有一个纽扣电池就是为了单片机掉电情况下可以长时间为RTC和LSE供电这样即使主电源断开RTC还能继续计时时间信息不会丢失。 我们不需要配置这儿蓝桥杯板上并没有LSE所以直接默认使用LSI即可LSE频率大约是32kHz虽然精度不如LSE高但足够满足RTC的基本计时需求。 8.3 编写代码 第一步打开CubeMX。 启用RTC时钟源激活RTC功能 在RTC Mode and Configuration标签页中 勾选Activate Clock Source启用RTC时钟源。勾选Activate Calendar启用日历功能。 配置预分频器RTC的时钟源LSI需要通过预分频器分频为1Hz也就是1秒/一次计数。 异步预分频Asynchronous Predivider31 计算公式异步分频后频率 LSI频率 / (Asynchronous值 1) 32kHz / (31 1) 1000Hz。这一步将高频信号降到1000hz 同步预分频Synchronous Predivider999 计算公式最终频率 异步分频后频率 / (Synchronous值 1) 1000Hz / (999 1) 1Hz。最终得到精确的1Hz信号驱动RTC的秒计数器 设置初始时间与日期在Calendar Time/Data中设置初始时间Time和日期Date 时间格式选择BIN二进制格式避免BCD编码的复杂性。这儿没设置代码中设置的BIN示例设置 时间12:30:00HOURS12, MINUTES30, SECONDS0。日期2023年10月1日YEAR23, MONTH10, DATE1。 最后还有一个在Hour Format选项中选择 RTC_HOURFORMAT_2424小时制–默认RTC_HOURFORMAT_1212小时制 完成生成代码 第二步开始编写代码。 首先先认识一下两个结构体 在STM32 HAL库中RTC的时间和日期是分开存储的分别用两个结构体来表示 RTC_TimeTypeDef用于存储时间时、分、秒。RTC_DateTypeDef用于存储日期年、月、日、星期。 这两个结构体是 HAL_RTC_GetTime 和 HAL_RTC_GetDate 函数的参数用于接收从RTC模块读取的时间和日期数据。因此必须先声明这两个结构体变量才能调用函数并存储读取到的数据 RTC_TimeTypeDef 结构体 typedef struct {uint8_t Hours; // 小时0-23 或 1-12uint8_t Minutes; // 分钟0-59uint8_t Seconds; // 秒0-59uint8_t TimeFormat; // 时间格式RTC_HOURFORMAT_AM或PM仅12小时制有效uint32_t SubSeconds; // 子秒未使用uint32_t SecondFraction; // 秒分数未使用uint32_t DayLightSaving; // 夏令时配置未使用uint32_t StoreOperation; // 存储操作未使用 } RTC_TimeTypeDef;RTC_DateTypeDef 结构体 typedef struct {uint8_t WeekDay; // 星期几1-71Mondayuint8_t Month; // 月份1-12uint8_t Date; // 日期1-31uint8_t Year; // 年份0-99表示2000-2099 } RTC_DateTypeDef;接下来我们得记住考试中用到的两个RTC函数。 HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format) HAL_StatusTypeDef HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)HAL_RTC_GetTime 功能从RTC模块读取当前时间。参数 hrtcRTC句柄。sTime存储时间的结构体RTC_TimeTypeDef。Format时间格式RTC_FORMAT_BIN 或 RTC_FORMAT_BCD。 HAL_RTC_GetDate 功能从RTC模块读取当前日期。参数 hrtcRTC句柄。sDate存储日期的结构体RTC_DateTypeDef。Format日期格式RTC_FORMAT_BIN 或 RTC_FORMAT_BCD。 非常简单直接代入 RTC_TimeTypeDef RTC_Time; RTC_DateTypeDef RTC_Data; __IO uint32_t uwTick_RTC_point;void Get_RTC(void) {if(uwTick - uwTick_RTC_point 100) return; //避免频繁进入uwTick_RTC_point uwTick;HAL_RTC_GetTime(hrtc,RTC_Time,RTC_FORMAT_BIN);HAL_RTC_GetDate(hrtc,RTC_Data,RTC_FORMAT_BIN); sprintf((char *)LCD_Test,Time:%02d-%02d-%02d ,RTC_Time.Hours,RTC_Time.Minutes,RTC_Time.Seconds); LCD_DisplayStringLine(Line7,LCD_Test); sprintf((char *)LCD_Test,Data:%02d-%02d-%02d ,RTC_Data.Year,RTC_Data.Month,RTC_Data.Date);LCD_DisplayStringLine(Line8,LCD_Test); }然后在循环中调用就好了 while (1){Get_RTC();}注%02d不是%2d用于少于两位在前面补零。 8.4 赛题实战 同8.3后续若有其他再补充。 九、补充 后续进行补充 按键双击、DMA 总结 自用
http://www.w-s-a.com/news/164452/

相关文章:

  • 个人网站免费服务器单页网站的域名
  • 网站设计简单讲解小店怎么做网站
  • 校园网站的意义wordpress去除更新
  • 网站开发用python吗常用的网页开发工具有哪些
  • 北京市住房建设投资建设网站做商城网站要哪些流程
  • seo网站改版杭州建设局官网
  • 物流网站建设策划书泰然建设网站
  • 百度做网站的费用采集发布wordpress
  • 网站运维公司有哪些防录屏网站怎么做
  • 昆明做网站seo的网站制作专业
  • 聊城制作手机网站公司wordpress 头条
  • 商城微网站模板一般电商网站做集群
  • winserver2008上用iis发布网站嵊州网站制作
  • 网站内页权重怎么查辽宁建设工程信息网怎么上传业绩
  • 丰都网站建设价格镇江网站制作费用
  • app手机网站建设黄网站建设定制开发服务
  • 百度网盘app下载徐州优化网站建设
  • 附近网站电脑培训班展台设计方案介绍
  • 河南便宜网站建设价格低上海高端室内设计
  • 保险网站有哪些平台wordpress会员vip购买扩展
  • 网站怎么做图片转换广州车陂网站建设公司
  • 下载flash网站网站设计书的结构
  • 水利建设公共服务平台网站放心网络营销定制
  • 设计网站过程wordpress+分页静态
  • 临海网站制作好了如何上线如果安装wordpress
  • 长沙 学校网站建设网站制作价格上海
  • 九江网站推广徽hyhyk1国家住房部和城乡建设部 网站首页
  • 阿克苏网站建设咨询动漫设计与制作属于什么大类
  • 网站编辑做多久可以升职wordpress版权修改
  • 网站开发维护成本计算国外外贸平台