• [技术干货] STM32 最小系统电路简析
    转自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
  • [交流分享] 聊聊万物互联-Wi-Fi6
    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似乎要在这个道路上越走越远,越来越远…
  • [技术干货] 利用51单片机写一个流水灯程序分享
    这里准备了一套 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)。 ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/25/144005vkgamxxyjqx26tfc.png) ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/25/144016w7b6phzx7ue6xjby.png) ## 3.热敏电阻器的温度特性 热敏电阻的电阻—温度特性曲线如下: 其中,1-NTC;2-CTR;3,4-PTC ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/25/144153sg5paj9mhorot5xq.png) ## 4.半导体热敏电阻传感器的应用 - NTC热敏电阻主要用于温度测量或温度补偿。 - PTC突变型热敏电阻主要用做温度开关、浪涌电流保护和恒温加热。 - CTR热敏电阻主要用做温度开关、浪涌电流保护。 - PTC缓变型热敏电阻主要用于在较宽的温度范围内进行温度补偿或温度测量。 如下图,热敏电阻用于CPU的温度测量 ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/25/144327tt1x2qwaidwzralp.png)
  • [技术干货] UWB定位的基本原理
    >超宽带(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飞行时间,直译为飞行时间测距法。 这个方法最大的特点就是实现起来简单,最大的缺点就是精度低,既然是高精度定位,那么使用这种方法就不太合适了。 ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/23/215813ogx8dvl0r0mvcppm.png) 以上测距方式理论上是说得过去的,但是其中**存在几个影响测距精度的因素:** 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的不良影响。 ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/forums/attachment/forum/202102/23/215824whxs8v3jxmxsyvdp.png)   
  • [新手课堂] CPU中的寄存器文件
    寄存器文件(register file)又称寄存器堆,是CPU中多个寄存器组成的阵列,通常由快速的静态随机读写存储器(SRAM)实现。这种RAM具有专门的读端口与写端口,可以多路并发访问不同的寄存器。CPU的指令集架构总是定义了一批寄存器,用于在内存与CPU运算部件之间暂存数据。在更为简化的CPU,这些架构寄存器(architectural registers)一一对应与CPU内的物理存在的寄存器。在更为复杂的CPU,使用寄存器重命名技术,使得执行期间哪个架构寄存器对应于哪个寄存器堆的物理存储条目(physical entry stores)是动态改变的。寄存器堆是指令集架构的一部分,程序可以访问,这与透明的CPU高速缓存(cache)不同。计算财务数据的时候,很多数据的计算是需要多步计算的,具体到每一步的计算,可能要分给不同的同事来算。如果每一步列一张小纸条,等某位同事算完第一个数,再抄到第二张小纸条给另外一位同事,这显然很慢很麻烦——直接列在一张小纸条上就好了嘛。另外,我们有这么多同事,分配小纸条的同事每次传小纸条可以一次分配好多张——当然,前提是分给不同的同事。因为可能一张纸条要算好几次,又有这么多纸条传来传去,因此为了不出错,我们需要标明这张小纸条给谁算,算完了,负责分配小纸条的同事根据下一步要算的,把标注的名字改一下给另外一位同事去算。CPU中的寄存器也一样,一个数据可能需要不同的计算单元多次处理,又有那么多的计算单元分成了好几组。所以我们需要多个寄存器,这些寄存器的组合称之为寄存器文件。每个计算单元只能处理特定名称的寄存器里面的数据,因此调度器经常需要对寄存器进行分配、重命名、退出等操作。
  • [技术干货] 小熊派-物联网+LoRa——实现LoRa组网(2.LoRa初始化)
    >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 作者:二进制君 ## 触摸按键电路板 由于触摸按键具有简洁,精美,使得产品看起来更加时尚,更容易获取当今客户的青睐,因而,越来越多的产品采用触摸按键来代替机械按键。 ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202012/15/191323ld4twrfdistrkc3w.png) 电容触摸按键的检测原理说起来也十分简单,即RC充电时间的变化实现的,其中最根本的原因是电容容量的变化。在周围环境,不变的情况下,键盘和大地之间的电容是一个很微小的固定值,而人和大地之间也存在着电容。当手指靠近触摸按键时,就相当于触摸按键与大地之间的电容并联了人与大地之间的电容,从而使总电容容量变大。 ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202012/15/191334zzbl7qwhzsgyneyf.png) ## 电容按键触摸原理 触摸按键的道理虽然看起来很简单,但细想起来却感觉十分奇妙。实际应用中,触摸按键检测基本采用专门的IC或者带有触摸按键检测外设的单片机来完成,如CH552,EC8228,PIC单片机,STM32等! ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202012/15/191546zp8ayuwqn3agswqo.png) ## 触摸按键PCB 1、一般触摸按键PCB绘制为圆形或者正方形。大部分应用根据手指的接触尺寸,触摸键盘设计为12mm*12mm。 2、触摸按键距离应尽量拉开距离,一般大于5mm,避免相互间的干扰,并且尽量使用大面积铺地包围触摸按键键盘。
  • [交流吐槽] Arm处理器模式与寄存器分配
    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,结果是未知的。
  • [技术干货] 使用LiteOS Studio揭秘LiteOS在STM32上如何运行
    摘在华为云社区博客文章  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)留言,谢谢!
  • [技术干货] 小熊派学习——获取MPU6050六轴原始数据
    >本贴使用小熊派开发板+MPU6050六轴传感器,获取加速度计以及陀螺仪信息。 # 一、实验准备 ## 1.实验环境 - 一块stm32开发板(推荐使用小熊派),以及数据线 - 已经安装STM32CubeMX - 已经安装KeilMDK,并导入stm32开发板对应的芯片包(小熊派使用的是STM32L431RCT6) - 一个MPU6050模块(IIC接口)以及杜邦线 ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202008/30/210008fo7vb37vyayhbht4.png) ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202011/23/211454y1kqh8cliqa7goka.png) ---- ## 2.目标效果 - 通过CubeMX创建工程并配置参数 - IIC方式通信,配置MPU6050寄存器 - 小熊派通过IIC,获取陀螺仪、加速度计数据 - 串口1重定向输出编码器转动的角度、角速度 # 二、通过CubeMX生产MDK工程 ## A.芯片选择 - **打开CubeMX,进入芯片选择:** ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202008/30/212026gwrodk54oyp4fcl0.png) - **选择自己的stm32芯片(即STM32L431RCT6):** ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202008/30/212556cpcaurxs79c0xlnq.png) --- ## B.时钟源RCC设置 - **更改系统时钟源** >系统时钟默认使用内部的高速时钟(HSI),选择使用HSE,时钟更精确 - 设置外部时钟对应的端口 ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202008/30/213059x5earvssmihl5hsf.png) - 配置时钟树 >STM32L431RCT6系统时钟最大可以为80MHz,我们配置到最大即可 ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202008/30/213359df4zzbsoimhq2cgg.png) --- ## C.参数配置(对应端口设置) ### **1)配置USART1** 使用USART,模式为异步,波特率为115200,无硬件流控制 ---- ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202008/31/113002az5kmov8zzgjmkkd.png) ---- ### **2)硬件IIC配置** ``` IIC(Inter-Integrated Circuit)其实是IICBus简称,所以中文应该叫集成电路总线,它是一种串行通信总线,使用多主从架构 ``` 我们使用小熊派的I2C1,小熊派引出的引脚为**PB7->I2C1_SDA,PB6->I2C1_SCL** 其他选项我们保持默认即可 ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202011/23/211758a2wp3eljqb59qb6v.png) ### 3)MPU6050模块 当然,我们还需要了解MPU6050模块的电路,更好的进行配置。当然我们也可以自主制作模块 **如果AD0脚(9脚)接地,IIC地址为0X68(不包含最低位);如果AD0脚接V3.3,则IIC地址为0X69(不包含最低位).** ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202011/23/212349tmiaru2tuniste0z.png) **可以看到,AD0引脚已经被拉低,所有默认IIC地址为0x68. ** **最后,生成代码就OK了** ## D.工程设置 一些基础的设置,包括工程名、存储位置、工程环境、工程中各个文件的组成 ---- ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202011/23/212548euc6o7cvi4cot4wz.png) ---- ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202008/31/114727qvkul4oarss1pxdq.png) ---- ## E.生成代码 ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202008/31/115001f5olm8dvgsctxvko.png) ---- # 三、编写相应代码 ## 1. 串口1输出重定向 >我们知道printf是打印函数,原理是根据传入的字符串参数格式化打印输出到stdout中。我们需要让printf打印到串口之中,只需要在usart.c文件中模仿printf写一个输出函数即可 - 在添加头文件 ```c /* USER CODE BEGIN 0 */ #include <stdarg.h> #include <string.h> #include <stdio.h> /* USER CODE END 0 */ ``` - 写输出函数 ```c /* USER CODE BEGIN 1 */ void UsartPrintf(UART_HandleTypeDef *huart, char *fmt,...) { unsigned char UsartPrintfBuf[296]; va_list ap; unsigned char *pStr = UsartPrintfBuf; va_start(ap, fmt); vsprintf((char *)UsartPrintfBuf, fmt, ap); //格式化 va_end(ap); while(*pStr != 0) { USART1->TDR = *pStr++; while((USART1->ISR & 0x40) == 0); } } //注意:在usart.h中添加void UsartPrintf(UART_HandleTypeDef *huart, char *fmt,...); //使用方法:UsartPrintf(&huart1;,"hello world\r\n"); /* USER CODE END 1 */ ``` >注意:自己添加的代码,需要在begin和end之间 ## 2.写MPU6050驱动 >我们创建两个文件,分别是mpu6050.h和mpu6050.c ### a).首先我们在mpu6050.h中宏定义相应的寄存器,方便后续使用。 >因为寄存器实在太多,就没有一一列出,可以在附件中查看哦~ ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202011/23/212837k0bqvm1dck0bwsmk.png) ### b).配置各个寄存器 >主要的函数为:IIC读写、初始化MPU6050、设置陀螺仪和加速度计满量程、设置采样率、读取数据 代码附件中有,不一一讲解。 **初始化函数** >初始化函数中,我们必须要保证IIC读写的正确,因此可以通过读某一个有确定值的寄出去,大致判断IIC通信的准确。此方法同样在SPI等通信协议时会使用到 ```c uint8_t MPUInit(void) { uint8_t res; extern I2C_HandleTypeDef hi2c1; HAL_I2C_Init(&hi2c1;); //初始化IIC /*上电后最好有一定延时,保证数据准确*/ HAL_Delay(500); MPU_Write_Byte(MPU_PWR_MGMT1_REG,0x80); //复位MPU6050,写入1000 0000 MPU_Write_Byte(MPU_PWR_MGMT1_REG,0x00);//sleep写入0,保持唤醒状态 MPU_Set_Gyro_Fsr(3); //陀螺仪满量程±2000 MPU_Set_Accel_Fsr(0); //加速度计满量程±2g MPU_Set_Rate(50); //设置采样率=50Hz MPU_Write_Byte(MPU_INT_EN_REG,0x00);//关闭所有中断 MPU_Write_Byte(MPU_USER_CTRL_REG,0x00); //关闭从iic MPU_Write_Byte(MPU_FIFO_EN_REG,0x00); //关闭FIFO MPU_Write_Byte(MPU_INTBP_CFG_REG,0x80); //INT引脚低电平有效 res = MPU_Read_Byte(MPU_DEVICE_ID_REG); //读取地址 UsartPrintf(&huart1;,"\r\nMPU6050:0x%2x\r\n",res); UsartPrintf(&huart1;,"\r\nMPU6050:0x%2x\r\n",MPU_ADDR); if(res == MPU_ADDR) //判断地址是否正确 { MPU_Write_Byte(MPU_PWR_MGMT1_REG,0x01); //001,pll,x轴为参考系统时钟源 MPU_Write_Byte(MPU_PWR_MGMT2_REG,0x00); //都不使用待机模式 MPU_Set_Rate(50); }else return 1; return 0; } ``` 需要注意到,mpu6050上电后需要有一定延时,保证数据的稳定。重启mpu6050会有睡眠模式,写入关闭睡眠。最后检测并判断IIC地址是否正确 ## 3.main函数 ```c /* USER CODE BEGIN PV */ short x; short y; short z; /* USER CODE END PV */ ``` ```c /* USER CODE BEGIN 2 */ MPUInit(); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ while(MPU_Get_Gyroscope(&x,&y,&z) !=0); HAL_Delay(1000); printf("\r\n陀螺仪:x=-,y=-,z=-\r\n",x,y,z); while(MPU_Get_Accelerometer(&x,&y,&z) != 0); HAL_Delay(1000); printf("\r\n加速度计:x=-,y=-,z=-\r\n",x,y,z); /* USER CODE BEGIN 3 */ } ``` # 四、编译+下载 **点击编译后,0 error,0 warning** ---- ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202008/31/121955njyncblcptrssxe7.png) **小熊派连接在电脑上,代码下载到开发板** ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202008/31/122123balqwmlnq0o1x39m.png) ---- # 五、连接硬件 ## 1.将小熊派串口1和电脑相连,即拨到 AT-MUC,按下复位键 ![image.png](https://bbs-img-cbc-cn.obs.cn-north-1.myhuaweicloud.com/data/attachment/forum/202008/31/163115mub9q4eizoub4uox.png) ## 2.连接MPU6050 我们只需要使用mpu6050的SDA\SCL\GND\VCC。 即: - SDA-->PB7 - SCL-->PB6 - GND-->GND - VCC-->5V ---- ## 3.效果 打开串口调试助手,我们手动转动开发板(包括模块),看见陀螺仪和加速度数据不断变化,当静止时数据保持稳定 。 **至此,我们已经可以通过小熊派以IIC获取六轴传感器原始数据信息** >附件中包含CubeMX工程、MDK工程
  • [交流分享] 编译
    编译(compile)是指将代码某一种语言转化为另一种语言,如机器码,java的字节码,甚至c代码(flex/bison)。解释(interpret)是指将按照代码所表达的逻辑进行特定的操作。编译器是指专门用来进行编译这一动作的程序或组件。解释器是指专门用来进行解释这一动作的程序或组件。从编译的行为我们会发现这通常是一个一次性的行为(有特殊情况,比如glsl,比如有JIT功能的解释器),所以并没有关闭编译器这一说法(硬要说的话是指当编译器正在编译的时候,杀掉编译器这个进程)。你在F12中打开的调试界面中会发现网页的代码主要是有3种:html、js和css。简单的说,html提供了网页的骨架,js提供了网页的交互,css提供了网页的外貌。理论上如果浏览器足够模块化,是可以禁用js或者css或者html,但是我所了解的这几乎没有意义。html、js、css由浏览器内部的解释组件解释运行,并不会将其转化为其他某种语言,因此没有它们的编译组件。前端的组成多为这3种代码。但后端可以非常多样。对于cgi来说,其既可以是c、c++、java的程序、也可以是php、python、js(nodejs)的脚本。服务器有若干条规则告诉它对于不同的文件应该如何处理,比如程序就执行,脚本就调用相应的程序解释(也可能由服务器直接解释)。注意这里的服务器完全可以在内部建立处理请求的方法(比如由nodejs中的express直接搭建的服务器),在这种情况下我们可以认为是服务器直接解释了脚本(脚本和服务器写在一起了)。一个常见的网络请求的通俗流程如下:浏览器向服务器发送请求服务器调用一个cgi程序/脚本服务器将其输出返回给浏览器浏览器显示(html/css)运行(js)而禁用某个解释器会导致第2步无法进行。注意这里的禁用其实是指去禁止服务器处理这类脚本的方法(比如删掉了处理php文件的规则,导致由php构建的网页失效),导致服务器无法处理请求。转自:https://www.zhihu.com/question/368003317/answer/987083150
  • [问题求助] NB模组无法进入PSM模式,用的移动NB卡,APN是CMNbiot。
    【功能模块】模块是移远BC35G,移动NB卡,APN是CMNBIOT【操作步骤&问题现象】目前在做设备的低功耗,需要发送完数据后立即进入PSM模式,但是配置后无效,研究多天无果,希望好心人帮帮我我的操作方法如下,设备上电后自动入网,可以看到返回+QLWEVTIND:0+QLWEVTIND:3返回的3表示已经注网AT+NMGS=8,32332E3033342E30           // 发送数据OK,AT+CPSMS=1,,,01000001,00000001,   //配置PSM参数OKAT+CPSMS?+CPSMS:1,,,01000001,00000001        //查询PSM参数OKAT+CEREG=5                OKAT+CEREG?+CEREG:5,1,3A13,0DB9AD20,9,,,,OK这条命令后面的几个参数都是逗号,返回值空,这是T3324和T3412的值,是配置PSM的关键,为什么我的查询返回的看不到,而且配置后模组的电流图没有任何变化,重启也没用
  • [技术干货] [LiteOS移植]目标芯片STM32F1 使用STM32CubeMX生成外设初始化代码
    根据上一篇文章中的分析,我们需要先使用STM32CubeMX软件生成一些用到的外设的初始化代码,也就是存放在Inc和Src文件夹中的这些代码。STM32CubeMX介绍STM32CubeMX是一个ST公司推出的工具,主要用于通过图形化界面为STM32微控制器生成C语言版本的初始化代码和工程,甚至生成后的工程可以再次通过图形化界面进行修改,极大降低了开发难度,提高了开发效率。能生成支持IAR,Keil、STM32CubeIDE和Makefile等多种形式的工程。能自动完成时钟树的配置和验证。芯片功耗评估。每款STM32单片机的支持包都可以以补丁的形式安装,非常灵活。可运行于Windows,Linux和macOS操作系统上。下载地址:https://www.st.com/zh/development-tools/stm32cubemx.html打开下载地址选择版本点击“获取软件”即可开始下载,下载之前可能需要注册一个账户。我使用的版本为6.0.1STM32F1支持包版本为1.8.0STM32CubeMX的安装双击“安装文件”,如下图。在随后弹出的“用户账户控制”中选择“是”。选择Next,如下图。勾选“接受用户条款”并点击“Next”,如下图。勾选第一个选项并点击Next,如下图。选择安装路径后点击“Next”,即可完成安装,如下图。打开上一步骤中安装好的STM32CubeMX,需要安装STM32F1的Package,可能会自动弹出提示你需要安装Package,如果没有弹出你可以手动打开“Help”->“Manage embedded software packages”,如下图。在弹出的对话框中选择STM32F1(你也可以根据你的目标平台进行选择),勾选STM32Cube MCU Package for STM32F1 Series,并点击“Install Now”,等待安装完成即可,如下图。根据用到的外设生成Makefile工程这里我使用到两个串口“Uart1”、“Uart2”和一个GPIO用于控制LED灯。Uart1:PA9 Tx  PA10 RxUart2:PA2 Tx  PA3 RxLED:PC13点击“File”->“New Project”,开始创建工程,如下图。在随后弹出来的对话框左侧输入MCU型号,我这里使用的是STM32F103RC,如下图。输入完成之后即可在右侧看到你输入MCU型号对应的多种封装,选择你所使用的封装并双击即可,如下图:配置调式接口我使用的调试器为ST-Link,所以我在“Pinout&Configuration”选项卡中选择“System Core”标签下的“SYS”,在“Debug”中选择“Serial Wire”,如下图所示。配置时钟树我使用了外部晶振并且我想将系统主频配置为72MHz,所以在“Pinout&Configuration”选项卡中选择“System Core”标签下的“RCC”,在“High Speed Clock”中选择“Crystal/Ceramic Resonator”,如下图所示。打开“Clock Configuration”选项卡,在中心的HCK Clock中输入72并按下回车,系统会询问我们是否自动进行配置,我们选择“是”即可,如下图。配置串口及GPIO口在“Pinout&Configuration”选项卡中选择“Connectivity”标签下的“USART1”,在“Mode”中选择“Asynchronous”,如下图所示。USART2也也上述配置相同,如下图所示。因为PC13作为控制LED的管脚,所以需要配置为输出模式,直接在右下角的搜索框中输入“PC13”即可快速定位PC13管脚的位置,点击该管脚选择“GPIO_Output”即可。 其余配置因为LiteOS_Lab中已经将系统时钟的中断处理函数写好了,所以需要在“Pinout&Configuration”选项卡中选择“System Core”标签下的“NVIC”,在“Code Generation”中取消生成“Time base”的中断处理函数,否则会产生冲突,如下图所示。在“Project Manager”选项卡中选择“Project”标签,为工程命名并选择生成工程的目录,将工程类型选择为“Makefile”,如下图所示。在“Project Manager”选项卡中选择“Code Generation”标签,勾选“Generate peripheral initialization as a pair of *.c/*.h files per peripheral”,勾选了该选项后将每个外设的初始化代码生成到一个独立的文件中,例如gpio相关初始化生成到gpio.c和gpio.h文件中,使代码尽可能去耦合。便于移植,如下图所示。这时点击右上角“GENERATE CODE”即可完成初始化代码及工程的生成。最终的工程文件夹中的文件如下图所示:后面提取时,主要用到Core文件夹中的代码。提取用到的初始化代码放入工程中安装好了VScode、Iot Link Studio和SDK后,你可以在你的C:\Users\用户名\.iotlink\sdk\IoT_LINK\targets目录中看到很多示例工程,其中“STM32L431_BearPi”工程就是“分析与思考”篇中讲解的工程,将其复制一份并重命名为“STM32F103RC”,如下图:打开“STM32F103RC”文件夹,Inc文件夹和Src文件夹分别对应刚才生成的工程中的Core目录中的Inc文件夹和Src文件夹,如下图:替换Src目录下的文件将之前使用STM32CubeMX创建的工程中的Core/Src下除了main.c的所有文件复制到Src文件夹中,出现下图所示“替换或跳过文件”选择“替换目标中的文件”:替换后的目录内容如下图所示:替换Inc目录下的文件将之前使用STM32CubeMX创建的工程中的Core/Inc下除了main.h的所有文件复制到Src文件夹中,出现下图所示“替换或跳过文件”选择“替换目标中的文件”:替换后的目录内容如下图所示:本章移植到此结束,工程中有部分和STM32L4相关的文件,我们在后续章节中再进行修改和移除。
  • [技术干货] [LiteOS移植]目标芯片STM32F1 分析与思考
    本篇讲述如何分析官方或者其他友商提供的工程文件,在此基础上写出自己的工程文件,这是一种分析的方法,只有掌握了这样的方法,你才能快速学习一个新的东西,会自己独立思考问题,不是所有项目都有很好的技术支持,大部分都是靠自己,甚至说明文档都很少,只能阅读别人的代码,从而知道其思想和内部原理。 由上至下整体分析拿到一份工程文件的第一件事不是直接去阅读其源码,如果直接去阅读源码可能会一头雾水,应该先分析其结构,在心中有个大的方向,再去阅读源码并更正心中的想法,这样才能做到最高效,在阅读的过程中还可以把觉得不好的地方记录下来,在自己后面开发得工程中加以改善。我们这里以SDK/targets/STM32L431_BEAR工程文件为例,带领大家阅读和分析。Demos:存放操作系统之上,用户逻辑代码。GCC:存放Makefile文件、链接脚本、编译过程中间文件。Hardware:存放平台特有硬件驱动代码(例如显示屏和各类拓展板)。Inc:存放头文件。OS_CONFIG:存放操作系统的配置文件。Src:存放单片机的外设初始化文件及main.c文件。uart_at:存放at使用的串口初始化和驱动代码文件。.config:Kconfig生成的配置文件.config_old:没使用到,可以忽略。Iot_config.h:Kconfig生成的配置头文件(主要通过宏定义来使能一些组件)。Kconfig:Kconfig读取该文件显示图形化配置选项。        根据以上分析,我们首先要将通过STM32CubeMX生成的工程调整结构,尽量和上面相同,因为工程的编译是依赖Makefile来进行,Makefile可以使用官方案例工程中提供的Makefile在GCC目录中,其原理是根据目录来指定需要被编译的文件,所以我们将自己的工程结构调整的尽可能与上面相同,便于适配Makefile,减少工作量。 Demo目录中有多个目录,每一个目录对应一个用户例程,我们可以如法炮制,根据自己的项目需求在Demo目录中新建一个xxxx(需求名称)的目录,并在目录中新建一个xxxx.c源文件,修改user_demo.mk,在其中添加上我们这个工程目录中源文件的信息,后面才能选择编译。GCC目录中存放多个链接脚本和Makefile文件,我们使用到的是Makefile、project.mk、os.ld这三个文件,重点修改project.mk和os.ld让其适配我们的工程目录和MCU型号。Hardware目录中存放板载外设的驱动,例如板子上特有的LCD显示屏、各种拓展板卡上的传感器,例如温度、湿度、光照传感器的驱动代码,我们可以移除没有用到的驱动代码,添加上自己开发板特有的传感器驱动,供用户代码调用。Inc目录存放单片机上各类外设的驱动代码的头文件,我们可以移除没有使用到的文件,添加上用到的单片机外设的驱动头文件,直接从STM32CubeMX生成的Makefile工程中的Inc目录中复制过来即可。OS_CONFIG目录中的文件用于控制LiteOS和调试信息的输出,我们可以根据需求,进行一些修改,例如通过target_config.h文件设置最大支持的信号量个数,是否采用硬件接管机制等等,一般不用设置,保持默认即可。Src目录中存放单片机上各类外设的驱动代码以及main函数的入口文件main.c,该文件直接使用即可,可以从STM32CubeMX生成的Makefile工程中的Src目录中复制一些用到的单片机外设驱动文件到该目录中,移除一些没有用到的文件。uart_at目录中存放发送AT指令控制外部支持AT指令的模块的串口驱动代码,包括串口初始化、实现一个数据结构,用于保存接收到的数据,供用户代码读取等。其余文件均不用修改,.config和iot_config.h文件由Kconfig根据SDK配置自动生成。下一节开始调整工程结构。