>

你好啊 👋

欢迎来到我的博客

电磁波与传输理论

符号 物理意义 国际单位 E 电场强度 伏特/米、牛顿/库仑 B 磁通量密度 特斯拉、韦伯/米2、伏特·秒/米2 D 电位移 库仑/米2、牛顿/伏特·米 H 磁场强度 安培/米 ∇⋅ 散度算符 /米 ∇× 旋度算符 ∂/∂t 对于时间的偏导数 /秒 S 曲面积分的运算曲面 米2 L 路径积分的运算路径 米 ds 微小面元素矢量 米2 dℓ 微小线元素矢量 米 ε0 电常数 法拉/米 μ0 磁常数 亨利/米、牛顿/安培2 ρf 自由电荷密度 库仑/米3 ρ 总电荷密度 库仑/米3 Qf 在闭曲面S里面的自由电荷 库仑 Q 在闭曲面S里面的总电荷 库仑 Jf 自由电流密度 安培/米2 J 总电流密度 安培/米2 If 穿过闭路径L所包围的曲面的自由电流 安培 I 穿过闭路径L所包围的曲面的总电流 安培 ΦB 穿过闭路径L所包围的曲面S的磁通量 特斯拉·米2、伏特·秒,韦伯 ΦE 穿过闭路径L所包围的曲面S的电通量 焦耳·米/库仑 ΦD 穿过闭路径L所包围的曲面S的电位移通量 库仑 电磁波基础 射频通常是指频率从100MHz~1000GHz之间的交变电流信号 ...

2025-05-09 · 3 min · 454 words · LXY

嵌入式-标准库-16-FLASH

简介 STM32F1系列FLASH包含程序存储器,系统存储器和选项字节三部分,通过闪存存储器接口(外设)可以对程序存储器和选项字节进行擦除和编程 读写FLASH用途: 利用程序存储器剩余空间来保存断电不丢失的用户数据 通过程序中编程(IAP,类似OTA),实现程序自我更新 在线编程(In-Circuit Programming - ICP)用于更新程序存储器的全部内容,通过JTAG,SWD协议或系统加载程序(Bootloader)下载内容 IAP(In-Application Programming)可以使用微控制器支持的任一种通信接口下载程序 了解 字-32=半字-16=字节-8 代码 /** * 函 数:FLASH读取一个32位的字 * 参 数:Address 要读取数据的字地址 * 返 回 值:指定地址下的数据 */ uint32_t MyFLASH_ReadWord(uint32_t Address) { //使用指针访问指定地址下的数据并返回 return *((__IO uint32_t *)(Address)); } /** * 函 数:FLASH读取一个16位的半字 * 参 数:Address 要读取数据的半字地址 * 返 回 值:指定地址下的数据 */ uint16_t MyFLASH_ReadHalfWord(uint32_t Address) { //使用指针访问指定地址下的数据并返回 return *((__IO uint16_t *)(Address)); } /** * 函 数:FLASH读取一个8位的字节 * 参 数:Address 要读取数据的字节地址 * 返 回 值:指定地址下的数据 */ uint8_t MyFLASH_ReadByte(uint32_t Address) { //使用指针访问指定地址下的数据并返回 return *((__IO uint8_t *)(Address)); } /** * 函 数:FLASH全擦除 * 参 数:无 * 返 回 值:无 * 说 明:调用此函数后,FLASH的所有页都会被擦除,包括程序文件本身,擦除后,程序将不复存在 */ void MyFLASH_EraseAllPages(void) { //解锁 FLASH_Unlock(); //全擦除 FLASH_EraseAllPages(); //加锁 FLASH_Lock(); } /** * 函 数:FLASH页擦除 * 参 数:PageAddress 要擦除页的页地址 * 返 回 值:无 */ void MyFLASH_ErasePage(uint32_t PageAddress) { //解锁 FLASH_Unlock(); //页擦除 FLASH_ErasePage(PageAddress); //加锁 FLASH_Lock(); } /** * 函 数:FLASH编程字 * 参 数:Address 要写入数据的字地址 * 参 数:Data 要写入的32位数据 * 返 回 值:无 */ void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data) { //解锁 FLASH_Unlock(); //编程字 FLASH_ProgramWord(Address, Data); //加锁 FLASH_Lock(); } /** * 函 数:FLASH编程半字 * 参 数:Address 要写入数据的半字地址 * 参 数:Data 要写入的16位数据 * 返 回 值:无 */ void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data) { //解锁 FLASH_Unlock(); //编程半字 FLASH_ProgramHalfWord(Address, Data); //加锁 FLASH_Lock(); } //存储的起始地址 #define STORE_START_ADDRESS 0x0800FC00 //存储数据的个数 #define STORE_COUNT 512 //定义SRAM数组 uint16_t Store_Data[STORE_COUNT]; /** * 函 数:参数存储模块初始化 * 参 数:无 * 返 回 值:无 */ void Store_Init(void) { /*判断是不是第一次使用*/ //读取第一个半字的标志位,if成立,则执行第一次使用的初始化 if (MyFLASH_ReadHalfWord(STORE_START_ADDRESS) != 0xA5A5) { //擦除指定页 MyFLASH_ErasePage(STORE_START_ADDRESS); //在第一个半字写入自己规定的标志位,用于判断是不是第一次使用 MyFLASH_ProgramHalfWord(STORE_START_ADDRESS, 0xA5A5); //循环STORE_COUNT次,除了第一个标志位 for (uint16_t i = 1; i < STORE_COUNT; i ++) { //除了标志位的有效数据全部清0 MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, 0x0000); } } /*上电时,将闪存数据加载回SRAM数组,实现SRAM数组的掉电不丢失*/ //循环STORE_COUNT次,包括第一个标志位 for (uint16_t i = 0; i < STORE_COUNT; i ++) { //将闪存的数据加载回SRAM数组 Store_Data[i] = MyFLASH_ReadHalfWord(STORE_START_ADDRESS + i * 2); } } /** * 函 数:参数存储模块保存数据到闪存 * 参 数:无 * 返 回 值:无 */ void Store_Save(void) { //擦除指定页 MyFLASH_ErasePage(STORE_START_ADDRESS); //循环STORE_COUNT次,包括第一个标志位 for (uint16_t i = 0; i < STORE_COUNT; i ++) { //将SRAM数组的数据备份保存到闪存 MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, Store_Data[i]); } } /** * 函 数:参数存储模块将所有有效数据清0 * 参 数:无 * 返 回 值:无 */ void Store_Clear(void) { //循环STORE_COUNT次,除了第一个标志位 for (uint16_t i = 1; i < STORE_COUNT; i ++) { //SRAM数组有效数据清0 Store_Data[i] = 0x0000; } //保存数据到闪存 Store_Save(); }

2024-06-09 · 2 min · 349 words · LXY

嵌入式-标准库-15-WDG

简介 监控程序运行状态,当程序因为设计漏洞,硬件故障,电磁干扰等原因出现卡死或者跑飞等现象时,看门狗可以及时复位程序,避免程序长时间罢工,保证系统可靠性和安全性 看门狗本质是一个定时器,当在指定时间范围内,程序没有执行喂狗,也就是重置计数器操作时,看门狗硬件电路就会自动产生复位信号 STM32内置两个看门口 独立看门狗(IWDG)独立工作,对时间精度要求比较低,LSI时钟独立的时钟 窗口看门狗(WWDG)要求在精准计时窗口起作用,时钟使用APB1 代码 独立看门狗 //RCC_GetFlagStatus()可以看是怎么启动的 /*IWDG初始化*/ //独立看门狗写使能 IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); //设置预分频为16 IWDG_SetPrescaler(IWDG_Prescaler_16); //设置重装值为2499,独立看门狗的超时时间为1000ms IWDG_SetReload(2499); //重装计数器,喂狗 IWDG_ReloadCounter(); //独立看门狗使能 IWDG_Enable(); //重装计数器,喂狗 IWDG_ReloadCounter(); 窗口看门狗 /*开启时钟*/ //开启WWDG的时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE); /*WWDG初始化*/ //设置预分频为8 WWDG_SetPrescaler(WWDG_Prescaler_8); //设置窗口值,窗326778口时间为30ms WWDG_SetWindowValue(0x40 | 21); //使能并第一次喂狗,超时时间为50ms WWDG_Enable(0x40 | 54); //重装计数器,喂狗 WWDG_SetCounter(0x40 | 54);

2024-06-09 · 1 min · 42 words · LXY

嵌入式-标准库-14-PWR

简介 Power Control 负责STM32内部电源供电部分,可以实现可编程电压检测器和低功耗模式功能 可编程电压检测器(PVD)可以检测VDD电压,当期下降到PVD阈值以下或以上会触发中断,用于执行紧急关闭任务 低功耗模式包括睡眠模式(Sleep),停机模式(Stop),待机模式(Standby),可在系统空闲时,降低STM32功耗 低功耗模式对比 电源框图 模式选择 代码 睡眠加串口收发 //在最后加WFI函数,可以被任何中断唤醒 //执行WFI指令,CPU睡眠,并等待中断唤醒 __WFI(); 函数进停止 PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI); //STM32进入停止模式,并等待中断唤醒 待机+实时时钟 /*使能WKUP引脚*/ //使能位于PA0的WKUP引脚,WKUP引脚上升沿唤醒待机模式 PWR_WakeUpPinCmd(ENABLE); //STM32进入停止模式,并等待指定的唤醒事件(WKUP上升沿或RTC闹钟) PWR_EnterSTANDBYMode(); /*待机模式唤醒后,程序会重头开始运行*/

2024-06-09 · 1 min · 25 words · LXY

嵌入式-标准库-13-RTC

用到的概念 时间 Unix 定义为UTC/GMT的1970年1月1日开始所经过的秒数,不考虑闰秒 GMT Greenwich Mean Time格林尼治(英国一个地名,这里有个天文台能观察)是一种以地球自转为基础的计量系统,将地球自转一周间隔等分为24小时以确定计时标准 UTC Universal Time Coordinated协调世界时是以原子钟为基础的时间计量系统,规定铯133原子基态的两个超精细能级间在零磁场下跃迁与地球自转一周时间相差超过00.9秒时,UTC会执行闰秒来保证计时与地球与自传协调一致 时间戳和时间转换 BKP 简介 Backup Registers备份数据器 由VBAT(1.8-3.6V)供电,只有短这个电数据才会丢失,(一般VBAT不接东西时,隔100nf电容到vdd(2.0-3.6v)) 不会被复位 TEMPER引脚产生侵入事件清除所有备份寄存器,这个引脚一般在VBAT旁边,接到设备外壳可以在设备被拆开时,触发时间,也能接其他事件,如设备锁死等 RTC引脚输出RTC校准时钟,RTC闹钟脉冲或者脉冲,一个引脚,只能选一个用 存储RTC时钟校准寄存器 用户数据存储容量 中小容量-20字节 大容量和互联型-84字节 结构 RTC 简介 Real Time Clock实时时钟 RTC是独立的定时器,STM32内部集成 VDD断电能由VBAT供电继续走时 32位可编程计数器 20位 c语言数字前加0表示8进制 #include "stm32f10x.h" // Device header #include <time.h> uint16_t MyRTC_Time[] = {2023, 1, 1, 23, 59, 55}; //定义全局的时间数组,数组内容分别为年、月、日、时、分、秒 void MyRTC_SetTime(void); //函数声明 /** * 函 数:RTC初始化 * 参 数:无 * 返 回 值:无 */ void MyRTC_Init(void) { /*开启时钟*/ //开启PWR的时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); //开启BKP的时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); /*备份寄存器访问使能*/ //使用PWR开启对备份寄存器的访问 PWR_BackupAccessCmd(ENABLE); //通过写入备份寄存器的标志位,判断RTC是否是第一次配置 if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) //if成立则执行第一次的RTC配置 { //开启LSE时钟 RCC_LSEConfig(RCC_LSE_ON); //等待LSE准备就绪 while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET); //选择RTCCLK来源为LSE RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //RTCCLK使能 RCC_RTCCLKCmd(ENABLE); //等待同步 RTC_WaitForSynchro(); //等待上一次操作完成 RTC_WaitForLastTask(); //设置RTC预分频器,预分频后的计数频率为1Hz RTC_SetPrescaler(32768 - 1); //等待上一次操作完成 RTC_WaitForLastTask(); //设置时间,调用此函数,全局数组里时间值刷新到RTC硬件电路 MyRTC_SetTime(); //在备份寄存器写入自己规定的标志位,用于判断RTC是不是第一次执行配置 BKP_WriteBackupRegister(BKP_DR1, 0xA5A5); } else //RTC不是第一次配置 { //等待同步 RTC_WaitForSynchro(); //等待上一次操作完成 RTC_WaitForLastTask(); } } //如果LSE无法起振导致程序卡死在初始化函数中 //可将初始化函数替换为下述代码,使用LSI当作RTCCLK //LSI无法由备用电源供电,故主电源掉电时,RTC走时会暂停 /* void MyRTC_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); PWR_BackupAccessCmd(ENABLE); if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) { RCC_LSICmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) != SET); RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); RCC_RTCCLKCmd(ENABLE); RTC_WaitForSynchro(); RTC_WaitForLastTask(); RTC_SetPrescaler(40000 - 1); RTC_WaitForLastTask(); MyRTC_SetTime(); BKP_WriteBackupRegister(BKP_DR1, 0xA5A5); } else { //即使不是第一次配置,也需要再次开启LSI时钟 RCC_LSICmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) != SET); RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); RCC_RTCCLKCmd(ENABLE); RTC_WaitForSynchro(); RTC_WaitForLastTask(); } }*/ /** * 函 数:RTC设置时间 * 参 数:无 * 返 回 值:无 * 说 明:调用此函数后,全局数组里时间值将刷新到RTC硬件电路 */ void MyRTC_SetTime(void) { //定义秒计数器数据类型 time_t time_cnt; //定义日期时间数据类型 struct tm time_date; //将数组的时间赋值给日期时间结构体 time_date.tm_year = MyRTC_Time[0] - 1900; time_date.tm_mon = MyRTC_Time[1] - 1; time_date.tm_mday = MyRTC_Time[2]; time_date.tm_hour = MyRTC_Time[3]; time_date.tm_min = MyRTC_Time[4]; time_date.tm_sec = MyRTC_Time[5]; //调用mktime函数,将日期时间转换为秒计数器格式 //- 8 * 60 * 60为东八区的时区调整 time_cnt = mktime(&time_date) - 8 * 60 * 60; //将秒计数器写入到RTC的CNT中 RTC_SetCounter(time_cnt); //等待上一次操作完成 RTC_WaitForLastTask(); } /** * 函 数:RTC读取时间 * 参 数:无 * 返 回 值:无 * 说 明:调用此函数后,RTC硬件电路里时间值将刷新到全局数组 */ void MyRTC_ReadTime(void) { //定义秒计数器数据类型 time_t time_cnt; //定义日期时间数据类型 struct tm time_date; //读取RTC的CNT,获取当前的秒计数器 //+ 8 * 60 * 60为东八区的时区调整 time_cnt = RTC_GetCounter() + 8 * 60 * 60; //使用localtime函数,将秒计数器转换为日期时间格式 time_date = *localtime(&time_cnt); //将日期时间结构体赋值给数组的时间 MyRTC_Time[0] = time_date.tm_year + 1900; MyRTC_Time[1] = time_date.tm_mon + 1; MyRTC_Time[2] = time_date.tm_mday; MyRTC_Time[3] = time_date.tm_hour; MyRTC_Time[4] = time_date.tm_min; MyRTC_Time[5] = time_date.tm_sec; }

2024-06-09 · 2 min · 310 words · LXY

嵌入式-标准库-12-SPI通信外设

简介 STN32内置了硬件SPI 可配置8位/16位数据帧,高位先行/低位先行,, 时钟频率:f(PCLK)/(,2,4,8,16,32,64,128,256) 支持多主机模型,主或从操作 可精简为半双工/单工通信 支持DMA 兼容I2S协议(数字音频传输协议) 需要理解 同样配置下,SPI1比SPI2频率大一倍 框图 主模式全双工连续传输 效率高,复杂 非连续 简单,效率低 代码 #include "stm32f10x.h" // Device header /** * 函 数:SPI写SS引脚电平,SS仍由软件模拟 * 参 数:BitValue 协议层传入的当前需要写入SS的电平,范围0~1 * 返 回 值:无 * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SS为低电平,当BitValue为1时,需要置SS为高电平 */ void MySPI_W_SS(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); //根据BitValue,设置SS引脚的电平 } /** * 函 数:SPI初始化 * 参 数:无 * 返 回 值:无 */ void MySPI_Init(void) { /*开启时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); //开启SPI1的时钟 /*GPIO初始化*/ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA4引脚初始化为推挽输出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA5和PA7引脚初始化为复用推挽输出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA6引脚初始化为上拉输入 /*SPI初始化*/ SPI_InitTypeDef SPI_InitStructure; //定义结构体变量 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //模式,选择为SPI主模式 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //方向,选择2线全双工 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //数据宽度,选择为8位 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //先行位,选择高位先行 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; //波特率分频,选择128分频 SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //SPI极性,选择低极性 SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //SPI相位,选择第一个时钟边沿采样,极性和相位决定选择SPI模式0 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS,选择由软件控制 SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC多项式,暂时用不到,给默认值7 SPI_Init(SPI1, &SPI_InitStructure); //将结构体变量交给SPI_Init,配置SPI1 /*SPI使能*/ SPI_Cmd(SPI1, ENABLE); //使能SPI1,开始运行 /*设置默认电平*/ MySPI_W_SS(1); //SS默认高电平 } /** * 函 数:SPI起始 * 参 数:无 * 返 回 值:无 */ void MySPI_Start(void) { MySPI_W_SS(0); //拉低SS,开始时序 } /** * 函 数:SPI终止 * 参 数:无 * 返 回 值:无 */ void MySPI_Stop(void) { MySPI_W_SS(1); //拉高SS,终止时序 } /** * 函 数:SPI交换传输一个字节,使用SPI模式0 * 参 数:ByteSend 要发送的一个字节 * 返 回 值:接收的一个字节 */ uint8_t MySPI_SwapByte(uint8_t ByteSend) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET); //等待发送数据寄存器空 SPI_I2S_SendData(SPI1, ByteSend); //写入数据到发送数据寄存器,开始产生时序 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET); //等待接收数据寄存器非空 return SPI_I2S_ReceiveData(SPI1); //读取接收到的数据并返回 } #include "stm32f10x.h" // Device header #include "MySPI.h" #include "W25Q64_Ins.h" /** * 函 数:W25Q64初始化 * 参 数:无 * 返 回 值:无 */ void W25Q64_Init(void) { MySPI_Init(); //先初始化底层的SPI } /** * 函 数:MPU6050读取ID号 * 参 数:MID 工厂ID,使用输出参数的形式返回 * 参 数:DID 设备ID,使用输出参数的形式返回 * 返 回 值:无 */ void W25Q64_ReadID(uint8_t *MID, uint16_t *DID) { MySPI_Start(); //SPI起始 MySPI_SwapByte(W25Q64_JEDEC_ID); //交换发送读取ID的指令 *MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交换接收MID,通过输出参数返回 *DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交换接收DID高8位 *DID <<= 8; //高8位移到高位 *DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); //或上交换接收DID的低8位,通过输出参数返回 MySPI_Stop(); //SPI终止 } /** * 函 数:W25Q64写使能 * 参 数:无 * 返 回 值:无 */ void W25Q64_WriteEnable(void) { MySPI_Start(); //SPI起始 MySPI_SwapByte(W25Q64_WRITE_ENABLE); //交换发送写使能的指令 MySPI_Stop(); //SPI终止 } /** * 函 数:W25Q64等待忙 * 参 数:无 * 返 回 值:无 */ void W25Q64_WaitBusy(void) { uint32_t Timeout; MySPI_Start(); //SPI起始 MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); //交换发送读状态寄存器1的指令 Timeout = 100000; //给定超时计数时间 while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01) //循环等待忙标志位 { Timeout --; //等待时,计数值自减 if (Timeout == 0) //自减到0后,等待超时 { /*超时的错误处理代码,可以添加到此处*/ break; //跳出等待,不等了 } } MySPI_Stop(); //SPI终止 } /** * 函 数:W25Q64页编程 * 参 数:Address 页编程的起始地址,范围:0x000000~0x7FFFFF * 参 数:DataArray 用于写入数据的数组 * 参 数:Count 要写入数据的数量,范围:0~256 * 返 回 值:无 * 注意事项:写入的地址范围不能跨页 */ void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count) { uint16_t i; W25Q64_WriteEnable(); //写使能 MySPI_Start(); //SPI起始 MySPI_SwapByte(W25Q64_PAGE_PROGRAM); //交换发送页编程的指令 MySPI_SwapByte(Address >> 16); //交换发送地址23~16位 MySPI_SwapByte(Address >> 8); //交换发送地址15~8位 MySPI_SwapByte(Address); //交换发送地址7~0位 for (i = 0; i < Count; i ++) //循环Count次 { MySPI_SwapByte(DataArray[i]); //依次在起始地址后写入数据 } MySPI_Stop(); //SPI终止 W25Q64_WaitBusy(); //等待忙 } /** * 函 数:W25Q64扇区擦除(4KB) * 参 数:Address 指定扇区的地址,范围:0x000000~0x7FFFFF * 返 回 值:无 */ void W25Q64_SectorErase(uint32_t Address) { W25Q64_WriteEnable(); //写使能 MySPI_Start(); //SPI起始 MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); //交换发送扇区擦除的指令 MySPI_SwapByte(Address >> 16); //交换发送地址23~16位 MySPI_SwapByte(Address >> 8); //交换发送地址15~8位 MySPI_SwapByte(Address); //交换发送地址7~0位 MySPI_Stop(); //SPI终止 W25Q64_WaitBusy(); //等待忙 } /** * 函 数:W25Q64读取数据 * 参 数:Address 读取数据的起始地址,范围:0x000000~0x7FFFFF * 参 数:DataArray 用于接收读取数据的数组,通过输出参数返回 * 参 数:Count 要读取数据的数量,范围:0~0x800000 * 返 回 值:无 */ void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count) { uint32_t i; MySPI_Start(); //SPI起始 MySPI_SwapByte(W25Q64_READ_DATA); //交换发送读取数据的指令 MySPI_SwapByte(Address >> 16); //交换发送地址23~16位 MySPI_SwapByte(Address >> 8); //交换发送地址15~8位 MySPI_SwapByte(Address); //交换发送地址7~0位 for (i = 0; i < Count; i ++) //循环Count次 { DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //依次在起始地址后读取数据 } MySPI_Stop(); //SPI终止 }

2024-06-06 · 3 min · 546 words · LXY

嵌入式-标准库-11-SPI

简介 SPI(Serial Peripheral Interface)是由Motorola公司开发的 四根通信线:SCK,MOSI,MISO,SS 同步,全双工 支持总线挂载多设备(一主多从) 需要了解 SS拉低有效起始条件,拉高终止 输出引脚配置为推挽输出,输入配置为浮空或上拉输入 SPI数据均是移位,要发送就必须接收,要接受就必须发送,一般空闲数据发送0xFF或0x00 没有表示确认收到的应答位 模式选择 对应模式 模式0-CPOL=0-CPHA=0 模式1-CPOL=0-CPHA=1 模式3-CPOL=1-CPHA=1 CPOL 为0,SCK空闲低电平 为1,SCK空闲高电平 CPHA 为1,SCK第一个边沿移出数据,第二个边沿移入数据 为0,SCK第一个边沿移入数据,第二个边沿移出数据 流程 发送数据看从机手册,一般是 读 发读指令 发地址,可能是多位 读取 写 发1=写指令 发地址,可能多位 写入,可以连续写入,自动跳下一位 以W25Q64为例 简介 低成本,小型化,使用简单,非易失性存储器 存储介质-Nor Flash(闪存) 时钟频率,80Mhz,160MHz(Dual SPI)/320MHz(Quad SPI) 存储容量,(24位地址) 存储空间先分块,再分扇区 需要了解 写入要先写使能 数据位只能1改0,不能0改1 写入数据必须先擦除,擦除后所有数据位变为1 擦除必须按最小单元进行 连续写入多字节时,最多写一页,超过会回到页首覆盖写入 写入操作后,芯片进入忙状态,不响应新的读写操作 直接调用读取时序,无需使能,无需额外操作,没有页限制,读取完不会进入忙状态,但是不能在忙状态读取 代码 #include "stm32f10x.h" // Device header /*引脚配置层*/ /** * 函 数:SPI写SS引脚电平 * 参 数:BitValue 协议层传入的当前需要写入SS的电平,范围0~1 * 返 回 值:无 * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SS为低电平,当BitValue为1时,需要置SS为高电平 */ void MySPI_W_SS(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); //根据BitValue,设置SS引脚的电平 } /** * 函 数:SPI写SCK引脚电平 * 参 数:BitValue 协议层传入的当前需要写入SCK的电平,范围0~1 * 返 回 值:无 * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCK为低电平,当BitValue为1时,需要置SCK为高电平 */ void MySPI_W_SCK(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue); //根据BitValue,设置SCK引脚的电平 } /** * 函 数:SPI写MOSI引脚电平 * 参 数:BitValue 协议层传入的当前需要写入MOSI的电平,范围0~0xFF * 返 回 值:无 * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置MOSI为低电平,当BitValue非0时,需要置MOSI为高电平 */ void MySPI_W_MOSI(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue); //根据BitValue,设置MOSI引脚的电平,BitValue要实现非0即1的特性 } /** * 函 数:I2C读MISO引脚电平 * 参 数:无 * 返 回 值:协议层需要得到的当前MISO的电平,范围0~1 * 注意事项:此函数需要用户实现内容,当前MISO为低电平时,返回0,当前MISO为高电平时,返回1 */ uint8_t MySPI_R_MISO(void) { return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6); //读取MISO电平并返回 } /** * 函 数:SPI初始化 * 参 数:无 * 返 回 值:无 * 注意事项:此函数需要用户实现内容,实现SS、SCK、MOSI和MISO引脚的初始化 */ void MySPI_Init(void) { /*开启时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟 /*GPIO初始化*/ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA4、PA5和PA7引脚初始化为推挽输出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA6引脚初始化为上拉输入 /*设置默认电平*/ MySPI_W_SS(1); //SS默认高电平 MySPI_W_SCK(0); //SCK默认低电平 } /*协议层*/ /** * 函 数:SPI起始 * 参 数:无 * 返 回 值:无 */ void MySPI_Start(void) { MySPI_W_SS(0); //拉低SS,开始时序 } /** * 函 数:SPI终止 * 参 数:无 * 返 回 值:无 */ void MySPI_Stop(void) { MySPI_W_SS(1); //拉高SS,终止时序 } /** * 函 数:SPI交换传输一个字节,使用SPI模式0 * 参 数:ByteSend 要发送的一个字节 * 返 回 值:接收的一个字节 */ uint8_t MySPI_SwapByte(uint8_t ByteSend) { uint8_t i, ByteReceive = 0x00; //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到 for (i = 0; i < 8; i ++) //循环8次,依次交换每一位数据 { MySPI_W_MOSI(ByteSend & (0x80 >> i)); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线 MySPI_W_SCK(1); //拉高SCK,上升沿移出数据 if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);} //读取MISO数据,并存储到Byte变量 //当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0 MySPI_W_SCK(0); //拉低SCK,下降沿移入数据 } return ByteReceive; //返回接收到的一个字节数据 } #ifndef __W25Q64_INS_H #define __W25Q64_INS_H #define W25Q64_WRITE_ENABLE 0x06 #define W25Q64_WRITE_DISABLE 0x04 #define W25Q64_READ_STATUS_REGISTER_1 0x05 #define W25Q64_READ_STATUS_REGISTER_2 0x35 #define W25Q64_WRITE_STATUS_REGISTER 0x01 #define W25Q64_PAGE_PROGRAM 0x02 #define W25Q64_QUAD_PAGE_PROGRAM 0x32 #define W25Q64_BLOCK_ERASE_64KB 0xD8 #define W25Q64_BLOCK_ERASE_32KB 0x52 #define W25Q64_SECTOR_ERASE_4KB 0x20 #define W25Q64_CHIP_ERASE 0xC7 #define W25Q64_ERASE_SUSPEND 0x75 #define W25Q64_ERASE_RESUME 0x7A #define W25Q64_POWER_DOWN 0xB9 #define W25Q64_HIGH_PERFORMANCE_MODE 0xA3 #define W25Q64_CONTINUOUS_READ_MODE_RESET 0xFF #define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID 0xAB #define W25Q64_MANUFACTURER_DEVICE_ID 0x90 #define W25Q64_READ_UNIQUE_ID 0x4B #define W25Q64_JEDEC_ID 0x9F #define W25Q64_READ_DATA 0x03 #define W25Q64_FAST_READ 0x0B #define W25Q64_FAST_READ_DUAL_OUTPUT 0x3B #define W25Q64_FAST_READ_DUAL_IO 0xBB #define W25Q64_FAST_READ_QUAD_OUTPUT 0x6B #define W25Q64_FAST_READ_QUAD_IO 0xEB #define W25Q64_OCTAL_WORD_READ_QUAD_IO 0xE3 #define W25Q64_DUMMY_BYTE 0xFF #endif #include "stm32f10x.h" // Device header #include "MySPI.h" #include "W25Q64_Ins.h" /** * 函 数:W25Q64初始化 * 参 数:无 * 返 回 值:无 */ void W25Q64_Init(void) { MySPI_Init(); //先初始化底层的SPI } /** * 函 数:MPU6050读取ID号 * 参 数:MID 工厂ID,使用输出参数的形式返回 * 参 数:DID 设备ID,使用输出参数的形式返回 * 返 回 值:无 */ void W25Q64_ReadID(uint8_t *MID, uint16_t *DID) { MySPI_Start(); //SPI起始 MySPI_SwapByte(W25Q64_JEDEC_ID); //交换发送读取ID的指令 *MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交换接收MID,通过输出参数返回 *DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交换接收DID高8位 *DID <<= 8; //高8位移到高位 *DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); //或上交换接收DID的低8位,通过输出参数返回 MySPI_Stop(); //SPI终止 } /** * 函 数:W25Q64写使能 * 参 数:无 * 返 回 值:无 */ void W25Q64_WriteEnable(void) { MySPI_Start(); //SPI起始 MySPI_SwapByte(W25Q64_WRITE_ENABLE); //交换发送写使能的指令 MySPI_Stop(); //SPI终止 } /** * 函 数:W25Q64等待忙 * 参 数:无 * 返 回 值:无 */ void W25Q64_WaitBusy(void) { uint32_t Timeout; MySPI_Start(); //SPI起始 MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); //交换发送读状态寄存器1的指令 Timeout = 100000; //给定超时计数时间 while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01) //循环等待忙标志位 { Timeout --; //等待时,计数值自减 if (Timeout == 0) //自减到0后,等待超时 { /*超时的错误处理代码,可以添加到此处*/ break; //跳出等待,不等了 } } MySPI_Stop(); //SPI终止 } /** * 函 数:W25Q64页编程 * 参 数:Address 页编程的起始地址,范围:0x000000~0x7FFFFF * 参 数:DataArray 用于写入数据的数组 * 参 数:Count 要写入数据的数量,范围:0~256 * 返 回 值:无 * 注意事项:写入的地址范围不能跨页 */ void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count) { uint16_t i; W25Q64_WriteEnable(); //写使能 MySPI_Start(); //SPI起始 MySPI_SwapByte(W25Q64_PAGE_PROGRAM); //交换发送页编程的指令 MySPI_SwapByte(Address >> 16); //交换发送地址23~16位 MySPI_SwapByte(Address >> 8); //交换发送地址15~8位 MySPI_SwapByte(Address); //交换发送地址7~0位 for (i = 0; i < Count; i ++) //循环Count次 { MySPI_SwapByte(DataArray[i]); //依次在起始地址后写入数据 } MySPI_Stop(); //SPI终止 W25Q64_WaitBusy(); //等待忙 } /** * 函 数:W25Q64扇区擦除(4KB) * 参 数:Address 指定扇区的地址,范围:0x000000~0x7FFFFF * 返 回 值:无 */ void W25Q64_SectorErase(uint32_t Address) { W25Q64_WriteEnable(); //写使能 MySPI_Start(); //SPI起始 MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); //交换发送扇区擦除的指令 MySPI_SwapByte(Address >> 16); //交换发送地址23~16位 MySPI_SwapByte(Address >> 8); //交换发送地址15~8位 MySPI_SwapByte(Address); //交换发送地址7~0位 MySPI_Stop(); //SPI终止 W25Q64_WaitBusy(); //等待忙 } /** * 函 数:W25Q64读取数据 * 参 数:Address 读取数据的起始地址,范围:0x000000~0x7FFFFF * 参 数:DataArray 用于接收读取数据的数组,通过输出参数返回 * 参 数:Count 要读取数据的数量,范围:0~0x800000 * 返 回 值:无 */ void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count) { uint32_t i; MySPI_Start(); //SPI起始 MySPI_SwapByte(W25Q64_READ_DATA); //交换发送读取数据的指令 MySPI_SwapByte(Address >> 16); //交换发送地址23~16位 MySPI_SwapByte(Address >> 8); //交换发送地址15~8位 MySPI_SwapByte(Address); //交换发送地址7~0位 for (i = 0; i < Count; i ++) //循环Count次 { DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //依次在起始地址后读取数据 } MySPI_Stop(); //SPI终止 }

2024-06-05 · 4 min · 710 words · LXY

嵌入式-标准库-10-I2C

简介 内部集成I2C收发电路,可由硬件自动执行时钟生成,起始终止条件生成,应答位首发,数据收发等功能,减轻CPU负担 支持多主机模型 支持7位/10位地址模型 支持不同通讯速度,标准速度100kHz,快速400kHz 支持DMA 兼容SMBus协议 STM32F103C8T6硬件I2C资源:I2C1,I2C2 可能用到 I2C寄存器 CR控制寄存器 DR数据寄存器 SR状态寄存器 10位地址时序 前5位11110地址前两位,地址8位 整体发两次是一个完整的地址 ![[Pasted image 20240605191111.png]] ![[Pasted image 20240605191429.png]] 主机发送传送序列图 主机接收传送序列图 代码 #include "stm32f10x.h" // Device header #include "MPU6050_Reg.h" #define MPU6050_ADDRESS 0xD0 //MPU6050的I2C从机地址 /** * 函 数:MPU6050等待事件 * 参 数:同I2C_CheckEvent * 返 回 值:无 */ void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT) { uint32_t Timeout; Timeout = 10000; //给定超时计数时间 while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS) //循环等待指定事件 { Timeout --; //等待时,计数值自减 if (Timeout == 0) //自减到0后,等待超时 { /*超时的错误处理代码,可以添加到此处*/ break; //跳出等待,不等了 } } } /** * 函 数:MPU6050写寄存器 * 参 数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述 * 参 数:Data 要写入寄存器的数据,范围:0x00~0xFF * 返 回 值:无 */ void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data) { I2C_GenerateSTART(I2C2, ENABLE); //硬件I2C生成起始条件 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //等待EV5 I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter); //硬件I2C发送从机地址,方向为发送 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //等待EV6 I2C_SendData(I2C2, RegAddress); //硬件I2C发送寄存器地址 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING); //等待EV8 I2C_SendData(I2C2, Data); //硬件I2C发送数据 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); //等待EV8_2 I2C_GenerateSTOP(I2C2, ENABLE); //硬件I2C生成终止条件 } /** * 函 数:MPU6050读寄存器 * 参 数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述 * 返 回 值:读取寄存器的数据,范围:0x00~0xFF */ uint8_t MPU6050_ReadReg(uint8_t RegAddress) { uint8_t Data; I2C_GenerateSTART(I2C2, ENABLE); //硬件I2C生成起始条件 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //等待EV5 I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter); //硬件I2C发送从机地址,方向为发送 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //等待EV6 I2C_SendData(I2C2, RegAddress); //硬件I2C发送寄存器地址 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); //等待EV8_2 I2C_GenerateSTART(I2C2, ENABLE); //硬件I2C生成重复起始条件 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //等待EV5 I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver); //硬件I2C发送从机地址,方向为接收 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); //等待EV6 I2C_AcknowledgeConfig(I2C2, DISABLE); //在接收最后一个字节之前提前将应答失能 I2C_GenerateSTOP(I2C2, ENABLE); //在接收最后一个字节之前提前申请停止条件 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED); //等待EV7 Data = I2C_ReceiveData(I2C2); //接收数据寄存器 I2C_AcknowledgeConfig(I2C2, ENABLE); //将应答恢复为使能,为了不影响后续可能产生的读取多字节操作 return Data; } /** * 函 数:MPU6050初始化 * 参 数:无 * 返 回 值:无 */ void MPU6050_Init(void) { /*开启时钟*/ RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE); //开启I2C2的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟 /*GPIO初始化*/ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB10和PB11引脚初始化为复用开漏输出 /*I2C初始化*/ I2C_InitTypeDef I2C_InitStructure; //定义结构体变量 I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; //模式,选择为I2C模式 I2C_InitStructure.I2C_ClockSpeed = 50000; //时钟速度,选择为50KHz I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; //时钟占空比,选择Tlow/Thigh = 2 I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; //应答,选择使能 I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //应答地址,选择7位,从机模式下才有效 I2C_InitStructure.I2C_OwnAddress1 = 0x00; //自身地址,从机模式下才有效 I2C_Init(I2C2, &I2C_InitStructure); //将结构体变量交给I2C_Init,配置I2C2 /*I2C使能*/ I2C_Cmd(I2C2, ENABLE); //使能I2C2,开始运行 /*MPU6050寄存器初始化,需要对照MPU6050手册的寄存器描述配置,此处仅配置了部分重要的寄存器*/ MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); //电源管理寄存器1,取消睡眠模式,选择时钟源为X轴陀螺仪 MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); //电源管理寄存器2,保持默认值0,所有轴均不待机 MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); //采样率分频寄存器,配置采样率 MPU6050_WriteReg(MPU6050_CONFIG, 0x06); //配置寄存器,配置DLPF MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); //陀螺仪配置寄存器,选择满量程为±2000°/s MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); //加速度计配置寄存器,选择满量程为±16g } /** * 函 数:MPU6050获取ID号 * 参 数:无 * 返 回 值:MPU6050的ID号 */ uint8_t MPU6050_GetID(void) { return MPU6050_ReadReg(MPU6050_WHO_AM_I); //返回WHO_AM_I寄存器的值 } /** * 函 数:MPU6050获取数据 * 参 数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767 * 参 数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767 * 返 回 值:无 */ void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ) { uint8_t DataH, DataL; //定义数据高8位和低8位的变量 DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H); //读取加速度计X轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L); //读取加速度计X轴的低8位数据 *AccX = (DataH << 8) | DataL; //数据拼接,通过输出参数返回 DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H); //读取加速度计Y轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L); //读取加速度计Y轴的低8位数据 *AccY = (DataH << 8) | DataL; //数据拼接,通过输出参数返回 DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H); //读取加速度计Z轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L); //读取加速度计Z轴的低8位数据 *AccZ = (DataH << 8) | DataL; //数据拼接,通过输出参数返回 DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H); //读取陀螺仪X轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L); //读取陀螺仪X轴的低8位数据 *GyroX = (DataH << 8) | DataL; //数据拼接,通过输出参数返回 DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H); //读取陀螺仪Y轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L); //读取陀螺仪Y轴的低8位数据 *GyroY = (DataH << 8) | DataL; //数据拼接,通过输出参数返回 DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H); //读取陀螺仪Z轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L); //读取陀螺仪Z轴的低8位数据 *GyroZ = (DataH << 8) | DataL; //数据拼接,通过输出参数返回 } 嵌入式-标准库-10-I2C通信外设 ...

2024-06-05 · 3 min · 456 words · LXY

嵌入式-标准库-8-I2C

简介 I2C(Inter IC BUS)是由Philips公司开发的一种通用数据总线 两根通信线 SCL(Serial Clock),SDA(Serial Data) 同步,半双工 带数据应答 支持总线挂载多设备(一主多从,多主多重) 使用到的 从机设备地址在I2C协议分7位和10位,7位常用,也就是类似于0000 001 一般7位的高位时厂商设置的,地位可以通过给特定引脚不同电平来改变 硬件电路 所有I2C设备连在一起,SDA连在一起 设备的SCL,SDA设置为开漏输出模式 SCL,SDA各添加一个上拉电阻,阻值一般4.7k左右 I2C时序基本单元 状态 起始条件 SCL高电平期间,SDA从高电平切换到低电平 终止条件 SCL高电平期间,SDA从低电平切换到高电平 发送一个字节 SCL低电平期间,主机将数据位依次放到SDA线上(高位先行) 然后释放SCL 从机在SCL高电平期间读取数据位, SCL高电平期间SDA不允许有数据变化 依次循环8次,即可发送一个字节 接受一个字节 SCL低电平期间,从机将数据位依次放到SDA线上(高位先行) 然后释放SCL 主机在SCL高电平期间读取数据位, SCL高电平SDA不能有数据变化 循环8次读取一个字节 主机在接收前需要释放SDA 发送应答 主机在接收完一个字节后,在下一个时钟发送一位数据,0表示应答,1表示非应答 接收应答 主机在发送完一个字节后,在下一个时钟接收一位数据,0表示应答,1表示非应答 (主机接收前需要释放SDA) 指定地址写 S开始在SCL高时拉低SDA SLAVE ADRESS从机地址主机在时钟低修改SDA,从机在时钟高读取SDA R/W-表示要读(1),还是写(0),这里是0 RA应答位,主机放,从机拉低主机跟随时钟下降沿释放SDA,从机同时拉低SDA Reg Address主机要读写的从机内的地址 RA应答位 Data读写的内容 RA P停止 指定地址读 在读的时候,直接读取,从机返回的是当前地址指针指向的数据,开始默认指向0,每读写一次,自动加一,如果想指向别的数据,先发送一个写入操作,但是不发送要写入的数据,然后执行读的操作就可以了 I2C规则是一旦SLAVE Address R/W给1了,从机就直接开始发数据,所以没有指定读哪里的方法,只能先发写然后发读来读指定地址 直接读 S开始在SCL高时拉低SDA SLAVE ADRESS从机地址主机在时钟低修改SDA,从机在时钟高读取SDA R/W-表示要读(1)还是写(0),这里是1 RA应答位,主机放,从机拉低主机跟随时钟下降沿释放SDA,从机同时拉低SDA Data从机发的数据主机释放,从机发数据 RA应答位 RA P停止 ...

2024-06-03 · 4 min · 780 words · LXY

嵌入式-标准库-8-USART

协议简介 简单双向串口通信有两根通信线 TX与RX要及交叉连接 只需单向数据传输时可只接一根通信线 电平标准不一致时,需加电平转接芯片 参数 波特率:串口通信的速率 起始位:数据帧开始,固定低电平 数据位:数据帧有效载荷,1高,0低,低位先行 校验位:数据验证,数据位计算得来 停止位:数据帧间隔,固定高电平 解释 波特率:单位码元/s,或者叫波特 比特率:bit/s,或者叫bps 在单片机上0,1算一位,这两个值相等,如果是多进制就不相等 STM32串口外设简介 USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器 USART是STM32内部集成的硬件外设 自带波特率发生器,最高4.5Mbits/s 可配置数据位长度(8/9) 可配置停止位长度(0.5/1/1.5/2) 可选校验位(无校验/奇校验/偶校验) 支持同步模式,硬件流控制,DMA,智能卡,IrDA,LIN STM32F103C8T6资源:USART1/2/3 ASCII字符 串口收发代码 uint8_t Serial_RxData; //定义串口接收的数据变量 uint8_t Serial_RxFlag; //定义串口接收的标志位变量 /** * 函 数:串口初始化 * 参 数:无 * 返 回 值:无 */ void Serial_Init(void) { /*开启时钟*/ //开启USART1的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启GPIOA的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); /*GPIO初始化*/ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //将PA9引脚初始化为复用推挽输出 GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //将PA10引脚初始化为上拉输入 GPIO_Init(GPIOA, &GPIO_InitStructure); /*USART初始化*/ //定义结构体变量 USART_InitTypeDef USART_InitStructure; //波特率 USART_InitStructure.USART_BaudRate = 9600; //硬件流控制,不需要 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //模式,发送模式和接收模式均选择 USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //奇偶校验,不需要 USART_InitStructure.USART_Parity = USART_Parity_No; //停止位,选择1位 USART_InitStructure.USART_StopBits = USART_StopBits_1; //字长,选择8位 USART_InitStructure.USART_WordLength = USART_WordLength_8b; //将结构体变量交给USART_Init,配置USART1 USART_Init(USART1, &USART_InitStructure); /*中断输出配置*/ //开启串口接收数据的中断 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); /*NVIC中断分组*/ //配置NVIC为分组2 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); /*NVIC配置*/ //定义结构体变量 NVIC_InitTypeDef NVIC_InitStructure; //选择配置NVIC的USART1线 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //指定NVIC线路使能 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路的抢占优先级为1 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的响应优先级为1 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //将结构体变量交给NVIC_Init,配置NVIC外设 NVIC_Init(&NVIC_InitStructure); /*USART使能*/ //使能USART1,串口开始运行 USART_Cmd(USART1, ENABLE); } /** * 函 数:串口发送一个字节 * 参 数:Byte 要发送的一个字节 * 返 回 值:无 */ void Serial_SendByte(uint8_t Byte) { //将字节数据写入数据寄存器,写入后USART自动生成时序波形 USART_SendData(USART1, Byte); //等待发送完成 while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); /*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/ } /** * 函 数:串口发送一个数组 * 参 数:Array 要发送数组的首地址 * 参 数:Length 要发送数组的长度 * 返 回 值:无 */ void Serial_SendArray(uint8_t *Array, uint16_t Length) { uint16_t i; //遍历数组 for (i = 0; i < Length; i ++) { //依次调用Serial_SendByte发送每个字节数据 Serial_SendByte(Array[i]); } } /** * 函 数:串口发送一个字符串 * 参 数:String 要发送字符串的首地址 * 返 回 值:无 */ void Serial_SendString(char *String) { uint8_t i; //遍历字符数组(字符串),遇到字符串结束标志位后停止 for (i = 0; String[i] != '\0'; i ++) { //依次调用Serial_SendByte发送每个字节数据 Serial_SendByte(String[i]); } } /** * 函 数:次方函数(内部使用) * 返 回 值:返回值等于X的Y次方 */ uint32_t Serial_Pow(uint32_t X, uint32_t Y) { //设置结果初值为1 uint32_t Result = 1; //执行Y次 while (Y --) { //将X累乘到结果 Result *= X; } return Result; } /** * 函 数:串口发送数字 * 参 数:Number 要发送的数字,范围:0~4294967295 * 参 数:Length 要发送数字的长度,范围:0~10 * 返 回 值:无 */ void Serial_SendNumber(uint32_t Number, uint8_t Length) { uint8_t i; //根据数字长度遍历数字的每一位 for (i = 0; i < Length; i ++) { //依次调用Serial_SendByte发送每位数字 Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0'); } } /** * 函 数:使用printf需要重定向的底层函数 * 参 数:保持原始格式即可,无需变动 * 返 回 值:保持原始格式即可,无需变动 */ int fputc(int ch, FILE *f) { //将printf的底层重定向到自己的发送字节函数 Serial_SendByte(ch); return ch; } /** * 函 数:自己封装的prinf函数 * 参 数:format 格式化字符串 * 参 数:... 可变的参数列表 * 返 回 值:无 */ void Serial_Printf(char *format, ...) { //定义字符数组 char String[100]; //定义可变参数列表数据类型的变量arg va_list arg; //从format开始,接收参数列表到arg变量 va_start(arg, format); //使用vsprintf打印格式化字符串和参数列表到字符数组中 vsprintf(String, format, arg); //结束变量arg va_end(arg); //串口发送字符数组(字符串) Serial_SendString(String); } /** * 函 数:获取串口接收标志位 * 参 数:无 * 返 回 值:串口接收标志位,范围:0~1,接收到数据后,标志位置1,读取后标志位自动清零 */ uint8_t Serial_GetRxFlag(void) { //如果标志位为1 if (Serial_RxFlag == 1) { Serial_RxFlag = 0; //则返回1,并自动清零标志位 return 1; } //如果标志位为0,则返回0 return 0; } /** * 函 数:获取串口接收的数据 * 参 数:无 * 返 回 值:接收的数据,范围:0~255 */ uint8_t Serial_GetRxData(void) { //返回接收的数据变量 return Serial_RxData; } /** * 函 数:USART1中断函数 * 参 数:无 * 返 回 值:无 * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行 * 函数名为预留的指定名称,可以从启动文件复制 * 请确保函数名正确,不能有任何差异,否则中断函数将不能进入 */ void USART1_IRQHandler(void) { //判断是否是USART1的接收事件触发的中断 if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { //读取数据寄存器,存放在接收的数据变量 Serial_RxData = USART_ReceiveData(USART1); //置接收标志位变量为1 Serial_RxFlag = 1; //清除USART1的RXNE标志位 USART_ClearITPendingBit(USART1, USART_IT_RXNE); //读取数据寄存器会自动清除此标志位 //如果已经读取了数据寄存器,也可以不执行此代码 } } 串口数据流 分HEX数据包,文本数据包,可能会用到状态机思想 ...

2024-06-01 · 3 min · 496 words · LXY