LSM6DSO(X)传感器驱动关键代码解析:read_data_polling例程(一)
<div class='showpostmsg'><p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;">之前介绍了如何通过read_data_polling快速实现传感器数据读取,今天将对read_data_polling例程里的代码功能做一个详细的介绍,通过本内容您将会对read_data_polling例程有一个深入的了解,包ST传感器Standard C驱动的结构,各代码中各函数的功能,以及如何应用在STM32以外的平台等。</span></span></p><p style="text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;">前两期帖子可通过以下网址获得:</span></span></p>
<p style="text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"><a href="https://bbs.eeworld.com.cn/thread-1157546-1-1.html" style="color:blue; text-decoration:underline">https://bbs.eeworld.com.cn/thread-1157546-1-1.html</a></span></span></p>
<p style="text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"><a href="https://bbs.eeworld.com.cn/thread-1157653-1-1.html" style="color:blue; text-decoration:underline">https://bbs.eeworld.com.cn/thread-1157653-1-1.html</a></span></span></p>
<p style="text-align:justify"> </p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;">打开例程对应的read_data_polling.c文件可以看到里边有很多宏定义,这些宏定义主要都是针对一些定制的平台(开发板)比如STEVAL_MKI109V3或NUCLEO_F411RE等,如果不是这些平台可以不用考虑这些定义。接着是一些变量,data_raw_开头的是从传感器里读取的加速度、角速度和温度的原数据,单位是LSB。一搬使用时为了更直观的理解需要通过对应的函数进行转换,acceleration_mg就是转换后的加速度数据,对应的单位是mg(毫g)。相应的还有angular_rate_mdps和temperature_degC,用来存放转换后的角速度和温度数据,最后一个”_”后边的字符表示对应的单位,比如角速度是mdps(毫度/秒),温度是摄氏度。</span></span></p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;">whoamI变量是用来存放读取的WHO_AM_I寄存器数据,几乎所有的ST传感器都有WHO_AM_I寄存器,对应的ID也比较统一,一搬是0x0F。WHO_AM_I可用来判断传感器的型号,更主要的是通过读取到的寄存器值与数据手册中的对比,来判断出单片机或处理器与传感器的通信是否正常。</span></span></p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;">rst变量用来存放sw_reset位的数据,判断传感器是否复位成功,后边会具体介绍。</span></span></p>
<p style="text-indent:21.0pt; text-align:justify"> </p>
<p style="text-indent:21.0pt; text-align:justify"> </p>
<p style="text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"></span></span></p>
<p style="text-align:justify"> </p>
<p style="text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"></span></span></p>
<p style="text-align:justify"> </p>
<p style="text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"> read_data_polling.c文件里一共有6个函数,platform_init、platform_delay、tx_com、platform_read、platform_write和lsm6dso_read_data_polling。platform_init是平台的初始化代码,只针对STEVAL_MKI109V3开发板,这个板子可以通过代码来设置传感器的电压,如果不是这个平台可以忽略这部分代码。platform_delay是一段延时,函数内使用了2种延时方式HAL_Delay、和osalThreadDelayMilliseconds,一个是裸跑,一个是针对操作系统。可根据实际情况保留对应的延时函数,去掉对应的宏定义让延时函数生效。和多数传感器一样,LSM6DSOX有一个上电启动时间(Turn-on time),通过数据手册可以看到是35ms。传感器上电后必需经过35ms毫秒后才能和MCU正常通信,platform_delay的作用是防止MCU在传感器没有启动完成前对它进行配置。</span></span></p>
<p style="text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"></span></span></p>
<p style="text-align:justify"> </p>
<p style="text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"></span></span></p>
<p style="text-align:justify"> </p>
<p style="text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"></span></span></p>
<p style="text-align:justify"> </p>
<p style="text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"></span></span></p>
<p style="text-align:justify"> </p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;">接下来是tx_com函数,它的作用是通过串口或USB虚拟串口等发送数据,这里可以根据实际情况启用对应的函数(可以去掉无用的宏定义和其它代码,只保留用到的函数)。platform_write和platform_read是传感器与MCU对应的寄存器读写函数。在对传感器的寄存器读写时,比如如果要从传感器的0x0F寄存器读取一个字节的数据到whoamI变量,执行lsm6dso_device_id_get函数后传感器驱动会调用platform_read函数里与传感器通信的接口,reg对应存存器地址0x0F,bufp是whoamI变量的地址,len为1。Handle对应的是MCU的相应接口,比如是I2C1,还是SPI2等等。</span></span></p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;">如果使用的MCU不是STM32,只需要把tx_com、platform_read和platform_write里边的内容改成与相应单片机对应的接口即可,tx_com比较容易理解,有些时候可以使用 printf替代。platform_read和platform_write函数比如使用了某款单片机,通信接口是I2C,这里只需要把寄存器地址reg,要发送或者接收的寄存器变量地址bufp,以及一次要发送或者要接收的数据长度len传递给对应MCU的I2C发送或者接收函数。需要注意的是这里使用了I2C地址是8位地址,有些MCU如果使用了7位地址需要做移位处理。如果是SPI需要注意reg的第7位是读写位。</span></span></p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"></span></span></p>
<p style="text-indent:21.0pt; text-align:justify"> </p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"></span></span></p>
<p style="text-indent:21.0pt; text-align:justify"> </p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"> 最后是整个例程的核心函数lsm6dso_read_data_polling,它主要功能是对传感器进行配置,然后循环读取传感器的data_ready信号。如果数据准备完成就读取对应的数据,再通过串口输入对应数据。stmdev_ctx_t dev_ctx声明了传感器的上下文( context),使用时需要把寄存器的读写函数和接口句柄传递给dev_ctx,这样驱动里调用dev_ctx的write_reg写寄存器数据时就会跳到platform_write函数。所以,传感器驱动不需要考虑你的寄存器读写函数起什么名,无论是platform_write还是stm32_i2c_write,驱动都只调用dev_ctx.write_reg,你只要把对应的函数名在这个地方传递给dev_ctx.write_reg即可。</span></span></p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"> 此时如果使用了STM32以外的单片机,因为没有统一的接口句柄,此时dev_ctx.handle设置为null(0)或者不需要设置。</span></span></p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"></span></span></p>
<p style="text-indent:21.0pt; text-align:justify"> </p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"></span></span></p>
<p style="text-indent:21.0pt; text-align:justify"> </p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"> 声明了上下文后,接着执行了platform_init函数,用来初始化对应的平台,然后执行了platform_delay延时函数,等传感器上电启动完成。接着通过lsm6dso_device_id_get把传感器的WHO_AM_I寄存器的数据读取到whoamI变量,然后判断whoamI是否等于LSM6DSO_ID。打开lsm6dso_device_id_get函数能看到它是通过lsm6dso_read_reg函数来读取LSM6DSO_WHO_AM_I寄存器,LSM6DSO_WHO_AM_I对应的是0x0FU。接着我们再看lsm6dso_read_reg函数,这里可以看出它调用了ctx->read_reg,也就是之前传递过去的platform_read函数,把接口句柄,寄存器地址,变量指针,和读取长度传递过去。</span></span></p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"></span></span></p>
<p style="text-indent:21.0pt; text-align:justify"> </p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"></span></span></p>
<p style="text-indent:21.0pt; text-align:justify"> </p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"></span></span></p>
<p style="text-indent:21.0pt; text-align:justify"> </p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"></span></span></p>
<p style="text-indent:21.0pt; text-align:justify"> </p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"> lsm6dso_reset_set函数用来对传感器进行复位,然后通过读取对应的值来判断复位是否完成。lsm6dso_reset_set函数里先读取了CTRL3_C寄存器,然后将sw_reset置1,又写回到CTRL3_C。作用是在不改变CTRL3_C寄存器其它位的情况下将sw_reset设置为1。我们再来看一下reg.sw_reset是什么,找到lsm6dso_ctrl3_c_t结构体,这里定义了8个变量,每个变量占1bit,一共是一个字节。宏定义DRV_BYTE_ORDER表示字节顺序,可以适合不同的平台,这里设置的是小端模式DRV_LITTLE_ENDIAN,即sw_reset表示BIT0,boot表示BIT7。打开通过数据手册可以看出reg.sw_reset对应CTRL3_C (12h)的第0位(SW_RESET),此位为1时器件复位(reset device),并且在传感器复位成功后此位会自动清除(This bit is automatically cleared.)。</span></span></p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"> 有了上边的经验我们就很容易理解lsm6dso_reset_get函数的作用了,lsm6dso_reset_get用来读取CTRL3_C (12h)寄存的数据,然后返回第0位。通过do while阻断代码执行,只有读取到的sw_reset为零时才继续执行后边的代码。</span></span></p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"></span></span></p>
<p style="text-indent:21.0pt; text-align:justify"> </p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"></span></span></p>
<p style="text-indent:21.0pt; text-align:justify"> </p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"></span></span></p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"></span></span></p>
<p style="text-indent:21.0pt; text-align:justify"> </p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;"></span></span></p>
<p style="text-indent:21.0pt; text-align:justify"> </p>
<p style="text-indent:21.0pt; text-align:justify"><span style="font-family:宋体;"><span style="font-size:20px;">估计你看到这里眼睛一定很累了,没错,我也写累了,一起休息一下吧。接下来我会通过另一篇帖子继续讲解lsm6dso_read_data_polling函数 里的其它代码功能,敬请期待。</span></span></p>
</div><script> var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;" style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
if(parseInt(discuz_uid)==0){
(function($){
var postHeight = getTextHeight(400);
$(".showpostmsg").html($(".showpostmsg").html());
$(".showpostmsg").after(loginstr);
$(".showpostmsg").css({height:postHeight,overflow:"hidden"});
})(jQuery);
} </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script> <p>期待大侠继续讲解lsm6dso_read_data_polling函数</p>
<p>辛苦了,写这么多内容,的花不少精力。</p>
页:
[1]