-
【功能模块】【操作步骤&问题现象】1、CAN1、CAN2使用125k波特率,核心板应用层 按照10ms间隔频率,发送30000笔长度为8的can数据包;2、扩展单片机的CAN口外接CAN盒,CAN盒展示到的can数据包只有28000个左右;3、在扩展单片机CAN驱动接口做断点调试,显示扩展单片机 获取到的 CAN数据包数量 与 CAN盒展示数据包量一致;4、CAN盒接收到 包与包之间时间范围为20-40ms;5、CAN1、CAN2使用125k波特率,核心板应用层 按照30ms间隔频率,发送30000笔长度为8的can数据包,CAN盒与断点都显示无丢包情况;6、扩展单片机不接核心板,单外接CAN盒,扩展单片机CAN1、CAN2形成回路, 按照10ms间隔频率 发送接收50000笔长度为8的can数据包,CAN盒显示无丢包情况,断点调试接收包数量也显示正常;【截图信息】数据包最后两字节0x75 0x30 -- 》0x7530是核心板发送的CAN数据包数量CAN盒与扩展单片机接线如图很短【日志信息】(可选,上传日志内容或者附件)请专家给与解答……
-
SHT751. 芯片结构:内部集成有温湿度传感器、信号放大调理、A/D 转换、I2C 总线2. 分辨率:湿度值输出分辨率为14 位,温度值输出分辨率为12位,可通过软件编程为12 位和8 位。3. 测量精度:湿度测量精度为士1.8%RH,温度测量精度为士0.3C4. 工作电压:2.4V~5.5V(注:不同工作电压下的温度系数不同)5. 引脚连接:SCK 接I2C 总线的时钟,VCC GND 电源和地,SDA 数据引脚6. 通信时序基本符合I2C 的通信时序,通信过程分为:启动传感器、发送命令、测温和复位4 个步骤。(1)启动步骤:选择供电电压后,将传感器通电,上电速率不能低于1V/ms,通电后传感器进入11ms的休眠期,在此期间不允许对传感器发送任何命令。(2)发送命令:在发送命令中,用一组“启动传输”时序来表示数据传输的初始化,内容包括:当SCL 时钟高电平时将SDA 翻转为低电平,紧接着SCL变为低电平,随后在SCL时钟为高电平时将SDA 翻转为高电平。后续命令包含三个地址位(目前只支持“ 000”)和5 个命令位(见命令列表)。S HT75 返回如下时序表明已正确的接收到指令:在第8 个SCL 时钟的下降沿之后,将SDA 下拉为低电平(ACK位),并且在第9个SCL时钟的下降沿之后,释放SDA (恢复高电平)(3)温度、湿度测量过程:发送一组测量命令(“ 00000101”表示测量相对湿度RH,“00000011”表示测量温度T)后,51单片机等待测量结束,该过程大约20/80/320ms (最多可能有30%的变化), 分别对应8/12/14 bit测量。SHT75通过下拉SDA引脚至低电平并进入空闲模式来表示测量结束。51单片机在再次触发SCL时钟前,必须等待这个“转换完成”信号来读出数据。接着传输2个字节的测量数据和1个字节的CRC奇偶校验。51单片机需要通过下拉SDA为低来确认每个字节。所有的数据传输从MSB开始,右值有效(例如:对于12bit数据,从第5个SCL时钟起算作MSB,而对于8bit数据, 首字节则无意义)。SHT75用CRC数据(CRC 计算公式X8+X5+X4+1)的确认位来表明通信结束。如果不使用CRC校验,51单片机可以在测量值LSB后,通过保持确认位ACK为高电平,来终止通信。在测量和通信结束后,SHT75自动转入休眠模式。
-
小型气象站的应用FT-QC7小型气象站主要有:校园气象站、自动气象站、超声波气象站、微型气象站、便携式气象站、农业气象站、水文监测站、雨量监测站等。可监测风向、风速、温度、湿度、气压、雨量、土壤温湿度等常规气象要素,具有自动记录、超限报警和数据通讯等功能。主要的应用场所有:工农业生产、旅游、科研、气象等城市环境监测和其它专业领域等。小型气象站的功能和特点有哪些?1、低功耗采集器:静态功耗小于50uA2、GPRS联网、支持扩展RJ45联网3、支持扩展传感器远传,30km以内lora透传,30km以外物联网卡传输4、支持LED屏显示最大兼容32768px5、支持扩展安卓屏显示、存储、扩展安卓屏支持2G数据存储、U盘数据导出6、支持modbus485传感器扩展7、太阳能充电管理MPPT自动功率点跟踪8、可选配2000mah-24Ah蓄电池9、配套物联网数据展示、存储、分析平台
-
本实例使用一个拨码开关和2个独立按键控制流水灯的各种不同变化模式。模式流水灯功能示意如图8.15所示。图8.15 模式流水灯功能示意图这里我们需要注意,当拨码开关SW3处于OFF时,LED停止不动,只有一个LED处于点亮,并且点亮的LED不会变化;而SW3处于ON状态时,流水灯处于流动状态。按键S1被按下后,LED流动方向是从上到下(D9到D2方向);导航按键S2被按下后,LED流动方向是从下到上(D2到D9)。本实例代码虽然比之前几个实例都要长,但是我们可以把它们解析来看,其实也不复杂。首先,接口部分代码如下,除了时钟和复位信号,还有拨码开关switch[0]、2个独立按键key_v[1]和key_v[0]、8个LED指示灯信号led[7:0]。module cy4(input ext_clk_25m, //外部输入25MHz时钟信号input ext_rst_n, //外部输入复位信号,低电平有效input[0:0] switch, //拨码开关SW3输入,ON -- 低电平;OFF-- 高电平input[1:0] key_v,//S1/S2两个按键输入,未按下为高电平,按下后为低电平output reg[7:0] led //8个LED指示灯接口);//-------------------------------------//按键抖动判断逻辑wire key; //所有按键值相与的结果,用于按键触发判断reg[3:0] keyr;//按键值key的缓存寄存器assign key = key_v[0] & key_v[1];always @(posedge ext_clk_25m or negedge ext_rst_n)if (!ext_rst_n) keyr <=4'b1111;else keyr <= {keyr[2:0],key};wire key_neg = ~keyr[2] & keyr[3]; //有按键被按下wire key_pos = keyr[2] & ~keyr[3]; //有按键被释放//-------------------------------------//定时计数逻辑,用于对按键的消抖判断reg[19:0]cnt;always @ (posedge ext_clk_25m or negedge ext_rst_n)if (!ext_rst_n) cnt <= 20'd0;else if(key_pos || key_neg) cnt<=20'd0;else if(cnt < 20'd999_999) cnt<= cnt + 1'b1;else cnt <= 20'd0;reg[1:0] key_value[1:0];always @(posedge ext_clk_25m or negedge ext_rst_n)if (!ext_rst_n) beginkey_value[0] <= 2'b11;key_value[1] <= 2'b11;endelse if(cnt == 20'd999_999) begin//定时键值采集key_value[0] <= key_v;key_value[1] <=key_value[0];endwire[1:0] key_press = key_value[1] & ~key_value[0]; //消抖后按键值变化标志位有了按键值标志信号key_press,我们接下来就用它来控制LED流水灯的2个指示信号,即LED流水灯工作使能信号led_en和LED流水灯方向控制信号led_dir。//------------------------------------//流水灯开启、停止和流动方向控制开关、按键值采集reg led_en; //LED流水灯工作使能信号,高电平有效reg led_dir; //LED流水灯方向控制信号,1--从高到低流动,0--从低到高流动always @ (posedge ext_clk_25m or negedge ext_rst_n)if(!ext_rst_n) beginled_en <= 1'b0;led_dir <= 1'b0;endelse begin//流水灯开启/停止控制if(!switch[0]) led_en <= 1'b1;else led_en <= 1'b0;//流水灯方向控制if(key_press[0]) led_dir <=1'b0;//从低到高流动else if(key_press[1]) led_dir<= 1'b1; //从高到低流动else ;end最后两个always语句,前者对24bit计数器delay做循环计数,用于产生LED流水灯变化的切换频率;后者则根据led_en和led_dir信号控制8个LED流水灯实现最终的工作与否以及流动方向控制。//------------------------------------//LED流水灯变化延时计数器reg[23:0] delay;always @ (posedge ext_clk_25m or negedge ext_rst_n)if(!ext_rst_n) delay <= 24'd0;else delay <= delay+1'b1;//-------------------------------------//流水灯开启、停止和流动切换控制always @ (posedge ext_clk_25m or negedge ext_rst_n)if(!ext_rst_n) led <=8'b1111_1110;else if((delay == 24'h3fffff)&& led_en) begincase (led_dir)1'b0: led <={led[6:0],led[7]}; //从低到高流动1'b1: led <={led[0],led[7:1]}; //从高到低流动default: ;endcaseendelse ;endmodule
-
为什么称之为单片机最小系统呢?单片机最小系统,也叫做单片机最小应用系统,是指用最少的原件组成单片机可以工作的系统。单片机最小系统的三要素就是电源、晶振、复位 电路,如图:这张最小系统的电路图选自 KST-51 开发板原理图,下面我们就照这张电路图 来具体分析最小系统的三要素:1.电源 这个很好理解,电子设备都需要供电,就连我们的家用电器(手电筒^_^)也不例外。目前主流单片机的电源分为5V和3.3V这两个标准,当然现在还有对电压要求更低的单片机系统,一般多用在一些特定场合。 这个单片机的STC89C52,它需要5V的供电系统,我们的开发板是使用USB口输出的5V直流直接供电的。从图2-1可以看到,供电电路在40脚和20脚的位置上,40脚接的是+5V,通常也称为VCC 或VDD,代表的是电源正极,20脚接的是GND,代表的是电源的负极。+5V和GND之间还有个电容。 这个地方我们还要普及一个看原理图的知识。电路原理图是为了表达这个电路的工作原理而存在的,很多器件在绘制的时候更多考虑的是方便原理分析,而不是表达各个器件实际位置。比如原理图中的单片机引脚图,引脚的位置我们是可以随意放的,但是每个引脚上有一个数字标号,这个数字标号代表的才是单片机真正的引脚位置。一般情况下,这种双列直插封装的芯片,左上角是1脚,逆时针旋转引脚号依次增加,一直到右上角是最大脚位,我们现在选用的单片机一共是40个引脚,因此右上角就是40(在表示芯片的方框的内部),如下图所示,要分清原理图引脚标号和实际引脚位置的区别。2、晶振 晶振,又叫晶体振荡器,从这个名字就可以看出来,它注定一生都要不停振荡的。 他起到的作用是为单片机系统提供基准时钟信号,类似于我们部队训练时喊口令的人,单片 机内部所有的工作都是以这个时钟信号为步调基准来进行工作的。STC89C52 单片机的 18 脚 和 19 脚是晶振引脚,我们接了一个 11.0592M 的晶振(它每秒钟振荡 11,059,200 次),外加 两个 20pF 的电容,电容的作用是帮助晶振起振,并维持振荡信号的稳定。3.复位电路 在上面一张图左侧是一个复位电路,接到了单片机的9脚RST(Reset)复位引脚上,现在着重讲一下复位对单片机的作用。单片机复位一般是3种情况:(1)上电复位(2)手动复位(3)程序自动复位 假如我们的单片机程序有100 行,当某一次运行到第50行的时候,突然停电了,这个时候单片机内部有的区域数据会丢失掉,有的区域数据可能还没丢失。那么下次打开设备的时候,我们希望单片机能正常运行,所以上电后,单片机要进行一个内部的初始化过程,这个过程就可以理解为上电复位,上电复位保证单片机每次都从一个固定的相同的状态开始工作。这个过程跟我们打开电脑电源开电脑的过程是一致的。 当我们的程序运行时,如果遭受到意外干扰而导致程序死机,或者程序跑飞的时候,我们就可以按下一个复位按键,让程序重新初始化重新运行,这个过程就叫做手动复位,最典型的就是我们电脑的重启按钮。 当程序死机或者跑飞的时候,我们的单片机往往有一套自动复位机制,比如看门狗,具体应用以后再了解。在这种情况下,如果程序长时间失去响应,单片机看门狗模块会自动复位重启单片机。还有一些情况是我们程序故意重启复位单片机。 电源、晶振、复位构成了单片机最小系统的三要素,也就是说,一个单片机具备了这三个条件,就可以运行我们下载的程序了,其他的比如LED小灯、数码管、液晶等设备都是属于单片机的外部设备,即外设。最终完成我们想要的功能就是通过对单片机编程来控制各种 各样的外设实现的。
-
一 产品特点:1 可以检测周围环境的声音强度,使用注意:此传感器只能识别声音的有无(根据震动原理)不能识别声音的大小或者特定频率的声音2 灵敏度可调(图中蓝色数字电位器调节)3 工作电压3.3V-5V4 输出形式 数字开关量输出(0和1高低电平)5 设有固定螺栓孔,方便安装6 小板PCB尺寸:3.2cm * 1.7cm二 模块接线说明1 VCC 外接3.3V-5V电压(可以直接与5v单片机和3.3v单片机相连)2 GND 外接GND3OUT 小板开关量输出接口(0和1)三 使用说明1 声音模块对环境声音强度最敏感,一般用来检测周围环境的声音强度。2 模块在环境声音强度达不到设定阈值时,OUT输出高电平,当外界环境声音强度超过设定阈值时,模块OUT输出低电平;3 小板数字量输出OUT可以与单片机直接相连,通过单片机来检测高低电平,由此来检测环境的声音;4 小板数字量输出OUT可以直接驱动本店继电器模块,由此可以组成一个声控开关;声敏小模块测试使用继电器模块与声敏模块构成声控开关两个小模块的VCC和GND分别外接电源,继电器的IN和声敏模块的OUT相连。声敏模块的滑动变阻器可以调节开关指示灯,当有声音可以检测到的时候,开关指示灯会亮,足够大的声音,会产生大电流,促使继电器产生滴答的声音
-
转自CSDN:耗子X一、最小系统的组成1.供电电路可以起到升降压,滤波,稳流,限流,限压,防短接等多种功能,确保供电时的电流电压干净稳定.2.外部晶振时钟是单片机的心脏,外部晶振给单片机提供外部时钟.STM32的内部时钟采用的是RC震荡电路,而外部电路可以用采用石英晶振起振获得外部时钟,石英起振比RC震荡电路的精度要高的多。3.BOOT选择单片机上电时可以选择启动模式,不同的启动模式对应不同的启动区域,具体如下(BOOT0,BOOT1分别对应单片机上的两个脚)i.使用JTAG\SWD以及正常运行时我们采用第一种方式(x,0)ii.系统存储器中预置了bootloader,能够进行ISP下载,也就是我们常用的串口烧录iii.第三种方式常用于调试,写入程序进SRAM后可以直接进行调试,这样子调试很方便很快,但SRAM重新上电后数据会被清空,也就是说写入的程序只能用一次.4.复位电路复位电路在特定条件下给单片机的复位脚发送复位信号(一般是拉低使能)二、最小系统实例1.STM32F103C8T6最小系统三、各部分组成简析1.供电电路设计我们常用的给单片机供电的来源一般是USB或者3.7v锂电池,USB电压是5v,3.7v的锂电池放电电压范围是2.5v~4.2v,而STM32需要的供电电压是3.3v,那么我们需要设计降压,稳压电路来获得3.3v的电压.最小系统中常采用AMS1117-3.3v正向降压稳压器作为处理电源的主要元件,其中C1,C2是输入电容,防止断电后出现电压倒置.C3,C4是滤波电容,抑制自激震荡和稳定输出电压.2.外部晶振原理单片机的PC14,PC15接外部低速时钟,采用32.768KHZ的晶振(石英表内部的晶振也是这个频率的,至于为什么不是十进制整数,这和进制换算有关系),PD0,PD1接外部高速时钟,用8MHZ的晶振.在用不到外部时钟的情况下,这四个脚可以做正常的IO口使用.在使用三脚晶振和四脚晶振时,可以只接脚的输入部分.在单片机内部的外部时钟脚输入和输出之间是存在一个增益很大的非门的(皮尔斯振荡器),R10是一个反馈电阻,用于保证非门工作在线性工作区,这样晶振更容易起振.旁边的两颗电容是匹配电容,32.768KHZ的一般为12.5PF,8M的选20-30PF,不同单片机推荐的匹配电容可能并不一致,具体看手册中的电气属性那一节.3.BOOT设计这里是使用了拨码器对BOOT0和BOOT1进行手动选择高低电平.其他还有很多接法,比如使用跳线帽,按钮等.由于BOOT0=1,BOOT1=1这种启动方式不常用,在用按钮方式选择高低电平的情况下,画电路时一般把BOOT1接地,BOOT0在默认置0,按钮按下时BOOT0置1.4.复位电路设计复位电路这里很好理解,按钮按下时单片机的脚低电平使能,触发复位.————————————————版权声明:本文为CSDN博主「耗子x」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/u012329621/article/details/114030229
-
WiFi的历史从802.11的FHFS,DSSS到802.11b的DSSS,到802.11a的OFDM,802.11g的ERP(将OFDM从5G迁移到了2.4G),到802.11n的更宽频带(40MHz)的OFDM技术,到802.11ac的进一步拓宽(80, 80+80,160MHz)的OFDM技术,到802.11ax的更窄的子载波(78.125kHz)的OFDM技术,到讨论中的802.11be的320MHz的OFDM技术。 OFDM技术从802.11a,大概2001年的时候,到现在,已经持续演进了20年了,它的抗多径效应,适合无线空间的复杂环境。 因为802.11ax(Wi-FI 6)希望覆盖更广的空间,所以把载波宽度进一步变小了。每一代的发展,频带基本是越来越宽,似乎是可以无限的把频宽扩展下去。但是这应该是存在问题的,多样化的需求下,大频宽是可能浪费频段的。毕竟有些地方只需要小的频宽就好了。802.11ax还定义了一个仅支持20MHz的模式,也是瞄准了万物互联的趋势下,小数据,低能耗的搭配。但是802.11be(TB Wi-Fi 7)又把带宽变得更大了,每一代总是希望能更快的。但是单纯更快有什么意思呢。 OFDM不止可以分频带,因为网络的特性,还可以分用户,这是Wi-Fi现在越来越看重的,希望越来越多的用户都是使用这个网络,而且还要能用。从802.11ac(2013)开始的Wi-Fi5已经引入了多用户的观念,对应的技术是MU-MIMO。 它是这么样的,不同的用户用的是不同的天线。 到了802.1ax(Wi-Fi6),支持上行的MU-MIMO,而且还把一个频段同时分配给了多个用户(OFDMA),每个用户最少可以只用2MHz的带宽。一下还把上行和下行都给加进去了。 不止如此,还加入了BSS Coloring技术,弱化Exposed Node的问题,提升密集网络覆盖下的并行性。 Wi-Fi6有这么多优势,其实它的主要着笔点在于密集用户,密集网络。想想现在Wi-Fi6的设备已经都出来1两年了,但是也并没有多改变生态。其实现在大家也觉得够用了,就像我自己,这种Wi-Fi6的理念对人这个用户来讲真的是有点超前了,哪里去找如此密集的人流和网络?办公场所,体育场?这些场合毕竟是少数。自动化生产车间也许是比较合适的,不过不是人流,而是物流。 Wi-Fi7似乎要在这个道路上越走越远,越来越远…
-
这里准备了一套 KST-51 开发板8 个小灯依次一个接一个的点亮,流动起来,也就是常说的流水灯。先来看 8 个 LED 的核心电路图,如图: 其中控制引脚 P0.0 经过 74HC245 控制了 DB0,P0.1 控制 DB1„„P0.7 控制 DB7。一个字节是 8 位,如果写一个 P0,就代表了 P0.0 到 P0.7 的全部 8 个位。比如写 P0 = 0xFE;转换成二进制就是 0b11111110,所以点亮 LED小灯的程序,实际上我们可以改成另外一种写法#include <reg52.h>sbit ADDRO = P1^0;sbit ADDR1 = P1^1;sbit ADDR2 = P1^2;sbit ADDR3 = P1^3;sbit ENLED = P1^4 ;void main (){ ENLED = 0 ; ADDR3 = 1; ADDR2 = 1; ADDR1 = 1; ADDRO = 0 ; P0 =0xFE; //向P0写入数据来控制LED小灯 while (1) ; //程序停止在这里) 通过上边这个程序可以看出来,可以通过 P0来控制所有的8 个 LED小灯的亮和灭。 下面要进行依次亮和灭,怎么办呢?从这里就可以得到方法了,如果想让单片机流水灯 流动起来,依次要赋给 P0 的数值就是:0xFE、0xFD、0xFB、0xF7、0xEF、0xDF、0xBF、 0x7F。 在 C 语言当中,有一个移位操作,其中<<代表的是左移,>>代表的是右移。比如 a = 0x01<<1;就是 a 的结果等于 0x01 左移一位。注意:移位都是指二进制移位,那么移 位完了,本来在第 0 位的 1 移动到了第一位上,移动完了低位是补 0 的。所以 a 的值最终是 等于 0x02。 还要用到另外一个运算符~,这个符号是按位取反的意思,同理按位取反也是针对二进 制而言。比如 a = ~(0x01); 0x01 的二进制是 0b00000001,按位取反后就是 0b11111110,那么 a 的值就是 0xFE 了。 #include <reg52.h>sbit ADDRO = P1^0;sbit ADDR1 = P1^1;sbit ADDR2 = P1^2;sbit ADDR3 = P1^3;sbit ENLED = P1^4 ;void main (){ unsigned int i = 0; //定义循环变量 i,用于软件延时 unsigned char cnt = 0;//定义计数变量cnt,用于移位控制 ENLED =0 ; ADDR3 = 1; ADDR2 = 1; ADDR1 = 1; ADDRO= 0 ; while (1) //主循环,程序无限循环执行该循环体语句{ P0 = ~(0x01 << cnt) ; //Po等于1左移cnt位,控制8个LED for (i=0; i<20000; i++); //软件延时 cnt++; //移位计数变量自加1 if(cnt >= 8) //移位计数超过7后,再重新从0开始 { cnt = 0; } }} 程序中 cnt 是 count 的缩写,计数的意思,是非常常用的一个变量名称。当 cnt 等于 0 的 时候,1 左移 0 位还是 1,那么写成二进制后就是 0b00000001,对这个数字按位取反就是 0b11111110,亮的是最右边的小灯。当 cnt 等于 7 的时候,1 左移 7 位就是 0b10000000,按 位取反后是 0b01111111,亮的是最左边的小灯。
-
## 1.半导体热敏电阻传感器的工作原理 半导体热敏电阻(简称热敏电阻)工作原理同金属热电阻一样,也是利用电阻随温度变化的特性测量温度。 不同的热敏电阻材料,具有不同的电阻-温度特性,按温度系数的正负,将其分为正温度系数热敏电阻(PTC),负温度系数热敏电阻(NTC)和临界温度系数热敏电阻(CTR)。 ## 2.热敏电阻的分类 热敏电阻的种类很多,分类方法也不相同。按热敏电阻的阻值与温度关系这一重要特性可分为以下几种。 - ①正温度系数热敏电阻器(PTC)。 - ②负温度系数热敏电阻器(NTC)。 - ③突变型负温度系数热敏电阻器(CTR)。   ## 3.热敏电阻器的温度特性 热敏电阻的电阻—温度特性曲线如下: 其中,1-NTC;2-CTR;3,4-PTC  ## 4.半导体热敏电阻传感器的应用 - NTC热敏电阻主要用于温度测量或温度补偿。 - PTC突变型热敏电阻主要用做温度开关、浪涌电流保护和恒温加热。 - CTR热敏电阻主要用做温度开关、浪涌电流保护。 - PTC缓变型热敏电阻主要用于在较宽的温度范围内进行温度补偿或温度测量。 如下图,热敏电阻用于CPU的温度测量 
-
>超宽带(Ultra Wide Band,UWB)技术是一种无线载波通信技术,它不采用正弦载波,而是利用纳秒级的非正弦波窄脉冲传输数据,因此其所占的频谱范围很宽。 转载文章,原文链接:https://www.cnblogs.com/wslblog/articles/9155995.html ## 一、UWB定位的实现 UWB定位技术主要以dw1000芯片为基础实现室内外高精度定位工作,之所以能够实现定位的关键性因素有如下一个方面: - 1.dw1000提供数据帧收发时纪录时间戳,这是能够进行两点间测距的基本条件,简单来说,通过计算数据在空中飞行时间*光速=数据飞行距离,从而测出两节点间的距离。 - 2.有了数据帧收发时间戳,那么就必须提供足够高的时钟精度,因为1ns的时间电磁波就传输了30cm,dw1000提供了LDE的微代码,通过PLL使得时钟达到了64G的频率,当然,这个时钟仅提供给LDE使用,使得dw1000具备了超高精度的时间戳,64G的时钟可以使得dw1000时钟分辨率为15.65ps。 - 3.在以上基础上,可以实现两点间测距的功能,那么如果需要实现定位呢,则需要一个终端分别和多个基站通信,分别得到终端与各个基站的距离,且,基站之间的位置与距离在部署前期通过测绘手段可以得到这些数据。 从而得到了终端在这个定位系统中的位置,一般使用球面相交法,通过输入终端离基站的距离,计算出精确的位置信息。 ## 二、TOF测距方式 TOF即 time of flight飞行时间,直译为飞行时间测距法。 这个方法最大的特点就是实现起来简单,最大的缺点就是精度低,既然是高精度定位,那么使用这种方法就不太合适了。  以上测距方式理论上是说得过去的,但是其中**存在几个影响测距精度的因素:** 1.当设备B在T2时刻收到POLL后需要等待一个固定的时间Tdelay然后在T3时刻发出RESPONSE数据包,那么,问题出现了,我们在此处讲的Tdelay是一个绝对时间单位,比如3000us,但是A,B设备都有自己的时钟源,并且要命的是时钟源的存在自我偏差,俗称PPM,比如:我们想Tdelay=3000us但是由于时钟源的偏差问题,导致真实时间过去了3000.5us,可是在设备A端进行计算的时候还是按照3000us的Tdelay进行计算,那么,因为时钟源的偏差引入的0.5us的时间就被错误的当成是数据飞行的时间了。这样导致的结果就是,两设备A,B的真实距离为1m,结果测试得到的距离为2.5m。 2.再一个,Tdelay必须要事先双方约定好。不能有丝毫的差异,这对于设备B来说有些苛刻,因为有时候设备B可能在Tdelay时间内无法将数据从芯片取出分析然后将要返回的RESPONSE数据包送入芯片内,并让芯片在T3时刻发送出去。出现这样的情况将会导致测试失败。 ## 三、TW_TOF测距方式 基于上述TOF的缺陷,引入了TW_TOF这种测距方式,用于消除TOF的不良影响。 
-
>LoRa作为低功耗广域网的一员,同NB-IoT一样在物联网的地位崇高;理论上通信距离最远可达15km,在城市中达3Km。但因为工作在非工业授权频段(而NB-IoT工作于授权频段,三大移动运营商嘛),因此为保证稳定、合理的应用,使用起来就相对复杂。本贴,将使用小熊派作为嵌入式平台,采用安信可Ra01(其内核为SX127x芯片),以SPI方式对其相应的寄存器进行读写,完成基本环境搭建。 前面,我们以及完成了LoRa最底层的代码编写,链接:[小熊派-物联网+LoRa——实现LoRa组网(1.驱动) ](https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=94987&page=1&extra=#pid574925)。本文,主要讲解如何初始化LoRa,即对相应寄存器读写,完成关键参数的配置。 ## 一、前提准备 >为了使得代码的逻辑更加清晰,我们这里在.h文件中定义三个结构体,用于存放LoRa的各个参数、任务状态以及工作模式。 ```c typedef struct _sLoRaSettings { uint32_t RFFrequency; //频率,单位Hz int8_t Power; //发射功率2~20 uint8_t SignalBw; // 带宽 [0: 7.8 kHz, 1: 10.4 kHz, 2: 15.6 kHz, 3: 20.8 kHz, 4: 31.2 kHz, // 5: 41.6 kHz, 6: 62.5 kHz, 7: 125 kHz, 8: 250 kHz, 9: 500 kHz, other: Reserved] 带宽 uint8_t SpreadingFactor;// 扩频因子 [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips] 扩频因子 uint8_t ErrorCoding; // 误码编码率 [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8] 编码率 uint16_t PreambleLength; //前导码长度 }tLoRaSettings; //定义模块状态机 typedef enum { RF_IDLE, //空闲状态 RF_BUSY, //模块执行任务中 RF_RX_DONE, //接收完成 RF_RX_TIMEOUT, //接收超时 RF_TX_DONE, //发送完成 RF_TX_TIMEOUT, //发送超时 RF_CAD_DETECTED, //CAD检测到前导码 RF_CAD_EMPTY, //CAD检测完成,没有检测到前导码 RF_CAD_TIMEOUT, //CAD超时 RF_UNKNOW_STATUS //异常状态机 }tRFProcessReturnCodes; //硬件工作模式 typedef enum{ LORA_OPMODE_SLEEP=0,//睡眠 LORA_OPMODE_STANDBY,//待机 LORA_OPMODE_SYNTHESIZER_TX, LORA_OPMODE_TRANSMITTER,//发送 LORA_OPMODE_SYNTHESIZER_RX,// LORA_OPMODE_RECEIVER,//接收 LORA_OPMODE_RECIVER_SINGLE,//单次接收 LORA_OPMODE_CAD,//CAD }LoRaOpModeType; ``` 同时,为了使得我们快速完成对各个寄存器的读写,我们通过宏定义来定义一些寄存器。 ## 二、设置LoRa模式的函数 >lora一共有八种模式,同时更具数据手册,初始化LoRa时需要不断改名其相应的模式 读写LoRa模式的函数如下: ```c //设置Lora模式 void SX127xSetLoRaMode(void) { if(0 != (Read127xReg(REG_LR_OPMODE) & RFLR_OPMODE_LONGRANGEMODE_ON)) { // &0x80 设置第七位,0-fsk,1-lora return; //如果已经是lora模式返回 } SX127xSetOpMode(LORA_OPMODE_SLEEP); Write127xReg(REG_LR_OPMODE,Read127xReg(REG_LR_OPMODE) | RFLR_OPMODE_LONGRANGEMODE_ON); //先设置为sleep,然后设置为lora模式 } //设置opMode void SX127xSetOpMode(LoRaOpModeType opMode) { if(opMode == SX127xGetOpMode()) { return; } Write127xReg(REG_LR_OPMODE,(Read127xReg(REG_LR_OPMODE) & RFLR_OPMODE_MASK) | opMode | RFLR_OPMODE_FREQMODE_ACCESS_LF); HAL_Delay(1); } //获取opMode LoRaOpModeType SX127xGetOpMode(void) { return (LoRaOpModeType)(Read127xReg(REG_LR_OPMODE) & RFLR_OPMODE_MASK); } ``` ## 三、设置载波频率 >LoRa工作在非工业授权频段,最好不要将频率设置为32的整数倍。需要向载波频率地址0x06开始连续写三个数,来表示频率。 ```c void SX127xSetFrf(void) { SX127xSetOpMode(LORA_OPMODE_SLEEP); Write127xReg( REG_LR_FRFMSB, Frequency[0]);//射频载波频率最高有效位 Write127xReg( REG_LR_FRFMID, Frequency[1]);//射频载波频率中间有效位 Write127xReg( REG_LR_FRFLSB, Frequency[2]);//射频载波频率最低有效位 } ``` ## 四、初始化函数 >初始化函数,参入一共结构体参数,完成对LoRa的载波频率、带宽、纠错码、CRC校验、超时时间、超时时间、功率、前导码长度设置。 ```c //SX127x初始化 void LoRa01Init(tLoRaSettings *stting) { LoRaInit(); //完成初始化 LoRa01Restart(); while(0x6c!=Read127xReg(0x06)) { UsartPrintf(&huart1,"error %d pi error\r\n",Read127xReg(0x06)); HAL_Delay(100); } UsartPrintf(&huart1,"SPI init OK\r\n"); SX127xSetLoRaMode(); //设置为lora模式 if(NULL != stting) {//复制配置信息 memcpy(&localSettingSave,stting,sizeof(tLoRaSettings)); } //setting指向备份数据,避免修改导致setting原值改变 stting = &localSettingSave; if(stting->SignalBw > 9) //带宽不大于9 { UsartPrintf(&huart1,"WARRING SignalBw setting error,auto fix\r\n"); stting ->SignalBw = 9; } if(stting->ErrorCoding > 4) { UsartPrintf(&huart1,"WARRING ErrorCoding setting error,auto fix\r\n"); stting->ErrorCoding = 4; } if(stting->ErrorCoding < 1) { UsartPrintf(&huart1,"WARRING ErrorCoding setting error,auto fix\r\n"); stting->ErrorCoding = 1; } if(stting->SpreadingFactor > 12) { UsartPrintf(&huart1,"WARRING SpreadingFactor setting error,auto fix\r\n"); stting->SpreadingFactor = 12; } if(stting->SpreadingFactor <6) { UsartPrintf(&huart1,"WARRING SpreadingFactor setting error,auto fix\r\n"); stting->SpreadingFactor = 6; } if(stting->Power > 20) { UsartPrintf(&huart1,"WARRING Power setting error,auto fix\r\n"); stting->Power = 20; } SX127xSetFrf(stting->RFFrequency); //设置载波频率 Write127xReg(REG_LR_MODEMCONFIG1,u8_BWList[stting->SignalBw] | u8_CRList[stting->ErrorCoding -1] | RFLR_MODEMCONFIG1_IMPLICITHEADER_OFF);//带宽、纠错码 Write127xReg(REG_LR_MODEMCONFIG2,u8_SFList[stting->SpreadingFactor-6] | RFLR_MODEMCONFIG2_TXCONTINUOUSMODE_OFF|RFLR_MODEMCONFIG2_RXPAYLOADCRC_ON|0x03);//设置SD,CRC,超时时间 Write127xReg(REG_LR_SYMBTIMEOUTLSB,0xFF);//设置超时时间 Write127xReg(REG_LR_MODEMCONFIG3,0x0C);//设置低速率(包长超过16ms必须打开),开启低速率 if(stting->Power > 17) { Write127xReg(REG_LR_PACONFIG,0x80+stting -> Power - 5); Write127xReg(0x4d,0x87);//更改为更大功率 }else{ Write127xReg(REG_LR_PACONFIG,0x80+stting->Power-2); Write127xReg(0x4d,0x84); //关闭更大功率 } Write127xReg(REG_LR_OCP,0x3B); //过流保护 //设置前导码长度 Write127xReg(REG_LR_PREAMBLELSB,stting->PreambleLength >> 8); //高八位前导码 Write127xReg(REG_LR_PREAMBLELSB,stting->PreambleLength&0x00ff); //第八位前导码 } ``` 注意,这里的 ```c while(0x6c!=Read127xReg(0x06)) { UsartPrintf(&huart1,"error %d pi error\r\n",Read127xReg(0x06)); HAL_Delay(100); } ``` 是为了等待SPI协议正确读取,可以读取其他的寄存器如版本号寄存器。
-
>现在触摸按键应用越来越多,但它的工作原理是什么呢 以下转自:https://baijiahao.baidu.com/s?id=1618471340016370024&wfr=spider&for=pc 作者:二进制君 ## 触摸按键电路板 由于触摸按键具有简洁,精美,使得产品看起来更加时尚,更容易获取当今客户的青睐,因而,越来越多的产品采用触摸按键来代替机械按键。  电容触摸按键的检测原理说起来也十分简单,即RC充电时间的变化实现的,其中最根本的原因是电容容量的变化。在周围环境,不变的情况下,键盘和大地之间的电容是一个很微小的固定值,而人和大地之间也存在着电容。当手指靠近触摸按键时,就相当于触摸按键与大地之间的电容并联了人与大地之间的电容,从而使总电容容量变大。  ## 电容按键触摸原理 触摸按键的道理虽然看起来很简单,但细想起来却感觉十分奇妙。实际应用中,触摸按键检测基本采用专门的IC或者带有触摸按键检测外设的单片机来完成,如CH552,EC8228,PIC单片机,STM32等!  ## 触摸按键PCB 1、一般触摸按键PCB绘制为圆形或者正方形。大部分应用根据手指的接触尺寸,触摸键盘设计为12mm*12mm。 2、触摸按键距离应尽量拉开距离,一般大于5mm,避免相互间的干扰,并且尽量使用大面积铺地包围触摸按键键盘。
-
ARM微处理器的工作状态一般有两种,并可在两种状态之间切换:第一种为ARM状态,此时处理器执行32位的字对齐的ARM指令;第二种为Thumb状态,此时处理器执行16位的、半字对齐的Thumb指令。在程序的执行过程中,微处理器可以随时在两种工作状态之间切换,并且,处理器工作状态的转变并不影响处理器的工作模式和相应寄存器中的内容。但ARM微处理器在开始执行代码时,应该处于ARM状态。 ARM处理器状态进入Thumb状态:当操作数寄存器的状态位(位0)为1时,可以采用执行BX指令的方法, 使微处理器从ARM状态切换到Thumb状态。此外,当处理器处于Thumb状态时发生异常(如IRQ、FIQ、Undef、Abort、SWI等),则 异常处理返回时,自动切换到Thumb状态。进入ARM状态:当操作数寄存器的状态位为0时,执行BX指令时可以使微处理器从Thumb状态切换到ARM状态。此外,在处理器进行异常处理时,把PC指针放入异常模式链接寄存器中,并从异常向量地址开始执行程序,也可以使处理器切换到ARM状态。 ARM处理器模式ARM微处理器支持7种运行模式,分别为:用户模式(usr):ARM处理器正常的程序执行状态。快速中断模式(fiq):用于高速数据传输或通道处理。外部中断模式(irq):用于通用的中断处理。管理模式(svc):操作系统使用的保护模式。数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于虚拟存储及存储保护。系统模式(sys):运行具有特权的操作系统任务。定义指令中止模式(und):当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真。 ARM微处理器共有37个32位寄存器,其中31个为通用寄存器,6个为状态寄存器。但是这些寄存器不能被同时访问,具体哪些寄存器是可编程访问的,取决 微处理器的工作状态及具体的运行模式。但在任何时候,通用寄存器R14~R0、程序计数器PC、一个或两个状态寄存器都是可访问的。ARM工作状态下的寄存器组织 通用寄存器:通用寄存器包括R0~R15,可以分为三类: ─ 未分组寄存器R0~R7; ─ 分组寄存器R8~R14 ─ 程序计数器PC(R15)未分组寄存器R0~R7: 在所有的运行模式下,未分组寄存器都指向同一个物理寄存器,他们未被系统用作特殊的用途,因此,在中断或异常处理进行运行模式转换时,由于不同的处理器运行模式均使用相同的物理寄存器,可能会造成寄存器中数据的破坏,这一点在进行程序设计时应引起注意。分组寄存器R8~R14 对于分组寄存器,他们每一次所访问的物理寄存器与处理器当前的运行模式有关。 对于R8~R12来说,每个寄存器对应两个不同的物理寄存器,当使用fiq模式时,访问寄存器R8_fiq~R12_fiq;当使用除fiq模式以外的其他模式时,访问寄存器R8_usr~R12_usr。 对于R13、R14来说,每个寄存器对应6个不同的物理寄存器,其中的一个是用户模式与系统模式共用,另外5个物理寄存器对应于其他5种不同的运行模式。采用以下的记号来区分不同的物理寄存器:R13_<mode>R14_<mode>其中,mode为以下几种模式之一:usr、fiq、irq、svc、abt、und。 寄存器R13在ARM指令中常用作堆栈指针,但这只是一种习惯用法,用户也可使用其他的寄存器作为堆栈指针。而在Thumb指令集中,某些指令强制性的要求使用R13作为堆栈指针。 由于处理器的每种运行模式均有自己独立的物理寄存器R13,在用户应用程序的初始化部分,一般都要初始化每种模式下的R13,使其指向该运行模式的栈空 间,这样,当程序的运行进入异常模式时,可以将需要保护的寄存器放入R13所指向的堆栈,而当程序从异常模式返回时,则从对应的堆栈中恢复,采用这种方式 可以保证异常发生后程序的正常执行。 R14也称作子程序连接寄存器(Subroutine Link Register)或连接寄存器LR。当执行BL子程序调用指令时,R14中得到R15(程序计数器PC)的备份。其他情况下,R14用作通用寄存器。与 之类似,当发生中断或异常时,对应的分组寄存器R14_svc、R14_irq、R14_fiq、R14_abt和R14_und用来保存R15的返回 值。寄存器R14常用在如下的情况: 在每一种运行模式下,都可用R14保存子程序的返回地址,当用BL或BLX指令调用子程序时,将PC的当前值拷贝给R14,执行完子程序后,又将R14的值拷贝回PC,即可完成子程序的调用返回。以上的描述可用指令完成:1、执行以下任意一条指令:MOV PC,LRBX LR2、在子程序入口处使用以下指令将R14存入堆栈:STMFD SP!,{<Regs>,LR}对应的,使用以下指令可以完成子程序返回:LDMFD SP!,{<Regs>,PC}R14也可作为通用寄存器。程序计数器PC(R15) 寄存器R15用作程序计数器(PC)。在ARM状态下,位[1:0]为0,位[31:2]用于保存PC;在Thumb状态下,位[0]为0,位 [31:1]用于保存PC;虽然可以用作通用寄存器,但是有一些指令在使用R15时有一些特殊限制,若不注意,执行的结果将是不可预料的。在ARM状态 下,PC的0和1位是0,在Thumb状态下,PC的0位是0。 R15虽然也可用作通用寄存器,但一般不这么使用,因为对R15的使用有一些特殊的限制,当违反了这些限制时,程序的执行结果是未知的。 由于ARM体系结构采用了多级流水线技术,对于ARM指令集而言,PC总是指向当前指令的下两条指令的地址,即PC的值为当前指令的地址值加8个字节。在ARM状态下,任一时刻可以访问以上所讨论的16个通用寄存器和一到两个状态寄存器。在非用户模式(特权模式)下,则可访问到特定模式分组寄存器,图2.3说明在每一种运行模式下,哪一些寄存器是可以访问的。寄存器R16: 寄存器R16用作CPSR(Current Program Status Register,当前程序状态寄存器),CPSR可在任何运行模式下被访问,它包括条件标志位、中断禁止位、当前处理器模式标志位,以及其他一些相关的控制和状态位。 每一种运行模式下又都有一个专用的物理状态寄存器,称为SPSR(Saved Program Status Register,备份的程序状态寄存器),当异常发生时,SPSR用于保存CPSR的当前值,从异常退出时则可由SPSR来恢复CPSR。 由于用户模式和系统模式不属于异常模式,他们没有SPSR,当在这两种模式下访问SPSR,结果是未知的。
-
摘在华为云社区博客文章 https://bbs.huaweicloud.com/blogs/222021 原作者:zhushy编者按:在LiteOS大揭秘系列,我们和读者们分享了《LiteOS是怎么在STM32上开始运行的》,从源码上静态分析了一遍LiteOS的启动流程。本文提供一种新的方式,即基于LiteOS一站式开发工具LiteOS Studio,通过单步调试,来动态分析LiteOS的启动流程,给开发者一个更直观的展示。了解LiteOS系统,我们可以先从它的启动流程开始。不同的芯片和编译工具,其启动流程可能会有一些差异,本文基于码云 LiteOS开源站点 master分支12月的代码,以STM32F769IDISCOVERY(ARM Cortex M7)开发板和GCC编译工具为例,使用LiteOS Studio的单步调试,动态分析LiteOS的启动流程。LiteOS Studio环境准备在开始前,需要准备好LiteOS Studio环境,包含LiteOS Studio安装、新建工程、编译、烧录,掌握LiteOS Studio如何调测等等,可以参考官网文档站点https://liteos.gitee.io/liteos_studio/#/project_stm32。如何搭建LiteOS Studio开发环境 请参考搭建Windows开发环境如何新建STM32F769IDISCOVERY的LiteOS工程 请参考 新建工程如何编译,烧录、调测,请分别参考 编译配置-编译代码,烧录配置-烧录,调试器-执行调试注意,如果开发板使用的是板载ST-LINK仿真器,需要刷为JLINK。请参考 st-link仿真器单步调测。另外,执行单步调测,默认停止在main()函数。LiteOS操作系统的启动是从main函数开始的。而ARM Cortex-M芯片从上电到执行main函数,中间经过了Reset_Handler等函数。LiteOS系统重启、复位等都是从Reset_Handler函数开始执行的。在LiteOS Studio工程找到文件.vscode\launch.json,把其中的postLaunchCommands属性下面的"b main"改为"b Reset_Handler"。如下图:重新开始调测,系统会暂停在Reset_Handler函数处。如下图:los_startup_gcc.S启动引导文件介绍当对STM32F769IDISCOVERY开发板进行上电操作或者复位操作时,该开发板会从异常向量表中获取Reset_Handler函数的地址并执行该函数。汇编文件targets\STM32F769IDISCOVERY\los_startup_gcc.S定义了该函数。los_startup_gcc.S是启动引导文件,从Reset_Handler开始到执行main函数,主要工作就是准备C代码的运行环境,具体包括:设置栈指针SP,对应语句 ldr sp, =_estack初始化中断向量,对应函数LoopCopyVectorInit初始化data段,对应函数LoopCopyDataInit初始化bss段,对应函数LoopFillZerobss初始化系统时钟,跳转到函数SystemInit跳转到 C 代码函数main代码如下:Reset_Handler: cpsid i ldr sp, =_estack /* set stack pointer *//* Copy the vector_ram segment initializers from flash to SRAM */ movs r1, #0 b LoopCopyVectorInit CopyVectorInit: ldr r3, =_si_liteos_vector_data ldr r3, [r3, r1] str r3, [r0, r1] adds r1, r1, #4LoopCopyVectorInit: ldr r0, =_s_liteos_vector ldr r3, =_e_liteos_vector adds r2, r0, r1 cmp r2, r3 bcc CopyVectorInit/* Copy the data segment initializers from flash to SRAM */ movs r1, #0 b LoopCopyDataInit CopyDataInit: ldr r3, =_sidata ldr r3, [r3, r1] str r3, [r0, r1] adds r1, r1, #4LoopCopyDataInit: ldr r0, =_sdata ldr r3, =_edata adds r2, r0, r1 cmp r2, r3 bcc CopyDataInit ldr r2, =_sbss b LoopFillZerobss/* Zero fill the bss segment. */FillZerobss: movs r3, #0 str r3, [r2], #4LoopFillZerobss: ldr r3, = _ebss cmp r2, r3 bcc FillZerobss/* Call the clock system initialization function.*/ bl SystemInit/* Call static constructors *//* bl __libc_init_array *//* Call the application's entry point.*/ bl main bx lrData段存放的是已经初始化的全局变量,需要从Flash中获取这些数据到RAM中。而bss段存放的是没有初始化的全局变量,因此Flash中并没有bss段的变量值,所以启动引导文件只是对RAM中的.bss段进行清零操作。los_startup_gcc.S启动引导文件中使用的_estack 、_si_liteos_vector_data、_s_liteos_vector、_e_liteos_vector、_sidata、_sdata 、_edata、_sbss、_ebss,这些符号都定义在targets\STM32F769IDISCOVERY\liteos.ld链接脚本中。链接脚本根据应用需要,设置堆栈大小和栈地址,并控制每个段的存放位置。对于中断向量和data段,既要放到Flash中,也需要放到RAM中,并通过链接脚本的AT关键字把Flash的地址设定为load地址。注:链接脚本中的相关代码可以访问https://gitee.com/LiteOS/LiteOS/blob/master/targets/STM32F769IDISCOVERY/liteos.ld查看。los_startup_gcc.S启动引导文件中除了定义Reset_Handler函数,还定义了其他中断异常处理函数Default_Handler,并为Default_Handler的每个异常处理程序提供弱别名。所谓弱别名,即具有相同名称的任何函数都将覆盖此处的函数。这样做可以防止用户使能了中断却没有设置中断处理程序时造成的崩溃。Default_Handler函数只是进入一个无限循环以保留系统状态供调试器检查。los_startup_gcc.S启动引导文件动态运行现在我们来单步调测运行los_startup_gcc.S,启动调测后,系统会暂停在Reset_Handler函数的第一行代码cpsid i,此语句用来关中断,执行前后,观察寄存器primask值的变化,会发现由0变为1。继续执行语句" ldr sp, =_estack",同样观察寄存器,寄存器sp的值变化了。如下图:继续运行单步调测,观察如何调用LoopCopyVectorInit和CopyVectorInit,实现把中断向量从Flash复制到RAM的。在调测过程中,寄存器的数值可能是10进制进行展示的,如果想查看其他进制展示的数值,可以在调测界面的监视器窗口输入$寄存器名称+进制代码来切换进制查看,如$r0,x来查看r0寄存器的16进制。详细的进制代码如下:xhexadecimaldsigned decimaluunsigned decimalooctaltbinaryaaddress进制切换如图所示:由于循环次数较多,如果想跨过中断向量的复制,继续下面的代码,可以设置断点,然后F5继续调测到断点处。如下图,我们在118行设置了断点,继续执行会完成向量表的复制,去执行数据段data的初始化。以此类推,通过Studio边调测、边分析启动过程的后续代码。当执行到语句“bl main”,再按F11跳入继续执行时,就会跳转到C代码的main函数。下文继续分析main函数。main函数介绍LiteOS的main函数定义在targets\STM32F769IDISCOVERY\Src\main.c。main函数主要负责LiteOS的初始化工作。代码如下:INT32 main(VOID){ HardwareInit(); PRINT_RELEASE("\n********Hello Huawei LiteOS********\n" "\nLiteOS Kernel Version : %s\n" "build data : %s %s\n\n" "**********************************\n", HW_LITEOS_KERNEL_VERSION_STRING, __DATE__, __TIME__); UINT32 ret = OsMain(); if (ret != LOS_OK) { return LOS_NOK; } OsStart(); return 0;}硬件初始化函数HardwareInit()和主要芯片相关,这里不做详细介绍。下面介绍LiteOS内核的初始化,代码如下:LITE_OS_SEC_TEXT_INIT UINT32 OsMain(VOID){ UINT32 ret;#ifdef LOSCFG_EXC_INTERACTION ret = OsMemExcInteractionInit((UINTPTR)&__bss_end); if (ret != LOS_OK) { return ret; }#endif /* 初始化动态内存池 */ ret = OsMemSystemInit((UINTPTR)&__bss_end + g_excInteractMemSize); if (ret != LOS_OK) { return ret; } /* * 配置最大支持的任务个数、信号量个数、互斥锁个数、 * 队列个数以及软件定时器个数,设置g_sysClock和 * g_tickPerSecond全局变量 */ OsRegister();#ifdef LOSCFG_SHELL_LK OsLkLoggerInit(NULL);#endif #ifdef LOSCFG_SHELL_DMESG ret = OsDmesgInit(); if (ret != LOS_OK) { return ret; }#endif/* * 初始化硬中断,此后LiteOS就会接管系统的中断, * 使用中断前需要先注册中断并使能 */ OsHwiInit(); /* * 设置中断向量的中断处理函数,包括 * Hard Fault硬件故障中断、 * Non Maskable Interrupt不可屏蔽中断(NMI)、 * Memory Management内存管理中断、 * Bus Fault 总线故障中断、 * Usage Fault使用故障中断、 * SVCall利用SVC指令调用系统服务的中断 */ ArchExcInit(); ret = OsTickInit(GET_SYS_CLOCK(), LOSCFG_BASE_CORE_TICK_PER_SECOND); if (ret != LOS_OK) { return ret; }#ifdef LOSCFG_PLATFORM_UART_WITHOUT_VFS uart_init();#ifdef LOSCFG_SHELL extern int uart_hwiCreate(void); /* HuaWeiChange */ uart_hwiCreate();#endif /* LOSCFG_SHELL */#endif /* LOSCFG_PLATFORM_UART_WITHOUT_VFS */ /* * 初始化任务链表包括任务的排序链表, * 初始化优先级消息队列链表(用于管理不同优先级任务) */ ret = OsTaskInit(); if (ret != LOS_OK) { PRINT_ERR("OsTaskInit error\n"); return ret; }#ifdef LOSCFG_KERNEL_TRACE ret = LOS_TraceInit(NULL, LOS_TRACE_BUFFER_SIZE); if (ret != LOS_OK) { PRINT_ERR("LOS_TraceInit error\n"); return ret; }#endif/* * 初始化任务监视器 */#ifdef LOSCFG_BASE_CORE_TSK_MONITOR OsTaskMonInit();#endif/* * OsIpcInit包括初始化消息队列链表、互斥锁链表和信号量链表 */ ret = OsIpcInit(); if (ret != LOS_OK) { return ret; } /* * CPUP should be inited before first task creation which depends on the semaphore * when LOSCFG_KERNEL_SMP_TASK_SYNC is enabled. So don't change this init sequence * if not necessary. The sequence should be like this: * 1. OsIpcInit * 2. OsCpupInit -> has first task creation * 3. other inits have task creation */#ifdef LOSCFG_KERNEL_CPUP ret = OsCpupInit(); if (ret != LOS_OK) { PRINT_ERR("OsCpupInit error\n"); return ret; }#endif/* * OsSwtmrInit对软件定时器和其在percpu上的排序链表进行初始化, * 并初始化定期器处理函数的内存池,同时还会创建软件定时器 * 的消息队列和定时器任务 */#ifdef LOSCFG_BASE_CORE_SWTMR ret = OsSwtmrInit(); if (ret != LOS_OK) { return ret; }#endif #ifdef LOSCFG_KERNEL_SMP (VOID)OsMpInit();#endif #ifdef LOSCFG_KERNEL_DYNLOAD ret = OsDynloadInit(); if (ret != LOS_OK) { return ret; }#endif #if defined(LOSCFG_HW_RANDOM_ENABLE) || defined (LOSCFG_DRIVERS_RANDOM) random_alg_context.ra_init_alg(NULL); run_harvester_iterate(NULL);#endif/* 创建空闲任务 */ ret = OsIdleTaskCreate(); if (ret != LOS_OK) { return ret; }#ifdef LOSCFG_KERNEL_RUNSTOP ret = OsWowWriteFlashTaskCreate(); if (ret != LOS_OK) { return ret; }#endif #ifdef LOSCFG_DRIVERS_BASE ret = OsDriverBaseInit(); if (ret != LOS_OK) { return ret; }#ifdef LOSCFG_COMPAT_LINUX (VOID)do_initCalls(LEVEL_ARCH);#endif #endif #ifdef LOSCFG_KERNEL_PERF ret = LOS_PerfInit(NULL, LOS_PERF_BUFFER_SIZE); if (ret != LOS_OK) { return ret; }#endif/* * LOSCFG_PLATFORM_OSAPPINIT宏默认已经在.config、menuconfig.h中定义。 * OsAppInit创建了一个名为“app_Task”的任务,该任务处理函数为 * app_init,任务优先级为10; * OsTestInit创建了一个名为“IT_TST_IN”的任务,该任务处理函数为 * TestTaskEntry,任务优先级为25。该函数暂时没有开源。 */#ifdef LOSCFG_PLATFORM_OSAPPINIT ret = osAppInit();#else /* LOSCFG_TEST */ ret = OsTestInit();#endif if (ret != LOS_OK) { return ret; } return LOS_OK;}完成内核的初始化后,调用OsStart()开始任务调度,自此LiteOS开始正常工作。OsStart函数的代码如下:LITE_OS_SEC_TEXT_INIT VOID OsStart(VOID){ LosTaskCB *taskCB = NULL; /* 获取当前执行任务的CPU ID,STM32F769是单核芯片,cpuid为0 */ UINT32 cpuid = ArchCurrCpuid(); /* * 配置Tick中断向量,其中断处理函数为OsTickHandler。 * 初始化System Tick Timer及其中断,并启动此Timer。 * 计数器会产生周期性中断 */ OsTickStart(); LOS_SpinLock(&g_taskSpin); /* 获取最高优先级任务队列中的第一个任务,赋给taskCB */ taskCB = OsGetTopTask();#ifdef LOSCFG_KERNEL_SMP /* * attention: current cpu needs to be set, in case first task deletion * may fail because this flag mismatch with the real current cpu. */ taskCB->currCpu = (UINT16)cpuid;#endif /* 设置32位的调度flag,第CPU ID位设置为1 */ OS_SCHEDULER_SET(cpuid); PRINTK("cpu %u entering scheduler\n", cpuid); /* * 调度g_runTask即taskCB任务,OsStartToRun函数 * 定义在los_dispatch.S汇编文件中 */ OsStartToRun(taskCB);}main函数动态运行现在我们来单步调测运行main.c源代码,LiteOS Studio在调测时,可以同步展示当前运行的源代码行,及对应的反汇编文件行,如下图:在调测过程中,变量的数值可能是10进制进行展示的,如果想查看其他进制展示的数值,可以在调测界面的监视器窗口输入变量名称名称+进制代码来切换进制查看,如memStart,x来查看变量memStart的16进制。如图:本期分享使用LiteOS Studio查看LiteOS启动过程,同时展示了使用LiteOS Studio调测的技巧,大家可以继续边调测、边分析后续的代码,会看到LiteOS整个启动流程:从板子复位上电开始,调用汇编代码Reset_Handler进入启动引导文件,完成C代码运行环境的准备工作、最后跳转到main函数。在main函数中完成硬件初始化和LiteOS内核的初始化,并通过汇编跳转到执行第一个最高优先级的任务命令的地址上,从而开始LiteOS的运行。欢迎大家分享使用LiteOS Studio调测LiteOS的心得,有任何问题、建议,都可以在开源LiteOS社区(https://gitee.com/liteos)留言,谢谢!
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签