因2020疫情影響,國內突然就冒出來一堆做體溫計的廠商。。。
硬件平臺:nRF52832 + JHM3000
JHM3000是個單線通信的體溫傳感器芯片,線性度較好,校準後精度可達0.1度。
主要問題是通信是單IO輸出,芯片上電後,IO就會不停地輸出脈衝信號,需要MCU計算脈寬來解碼數據。
每個採樣週期爲:2.8ms間隔 + 6ms數據傳輸
相鄰兩個高脈寬之比決定數據類型,START=3:3, BIT0=5:1, BIT1=1:5
數據樣本:91,3,3,1,6,1,5,5,1,1,5,5,1,1,5,1,5,6,1,1,5,6,1,1,5,5,2,1,5,5,1,91,3,4,...
每次採樣共1個START+14個BIT數據:S+BBBBBBBBBBBBB
解碼數據的時候,需要先找到START數據,然後纔開始解析採樣數據。
下面是主要源碼:
/*
* JHM3000 體溫傳感器驅動程序
* 每次採樣2.8ms + 數據傳輸6ms
* 單線IO輸出,相鄰兩個高脈寬之比決定數據,START=3:3, BIT0=5:1, BIT1=1:5
* 數據樣本:61,4,3,1,5,1,6,5,1,1,6,5,1,1,5,1,5,1,5,6,1,1,5,6,1,5,1,1,6,1,5,
* 91,3,3,1,6,1,5,5,1,1,5,5,1,1,5,1,5,6,1,1,5,6,1,1,5,5,2,1,5,5,1,
* 91,3,4,...
* 每次採樣共1個START+14個BIT數據:S+BBBBBBBBBBBBB
* 蔣曉崗<[email protected]>
* 2020.03.06
*/
#include <stdint.h>
#include <stdbool.h>
#include "log.h"
#include "bsp.h"
#include "sdk.h"
//判斷數據類型,錯誤返回0, 成功返回1,S位bit=-1,BIT10,BIT1返回1
static int decode_one_bit(uint32_t t1, uint32_t t2, int *bit)
{
uint32_t diff;
if((t1 + t2) > 10)
{
LOG("t1=%d, t2=%d\r\n", t1, t2);
return 0;
}
if(t1 > t2)
{
diff = t1 - t2;
if(diff > t2)
{
*bit = 0;
return 1;
}
}
if(t2 > t1)
{
diff = t2 - t1;
if(diff > t1)
{
*bit = 1;
return 1;
}
}
*bit = -1;
return 1;
}
//等待管腳電平跳變
static int wait_pin_state(int state, uint32_t timeout)
{
uint32_t diff;
uint32_t tick;
diff = 0;
tick = app_timer_cnt_get();
timeout = timeout << 5; //1毫秒約等於32個時鐘
while(diff < timeout)
{
if(nrf_gpio_pin_read(HM3000_PIN_DATA) == state)
{
return 1;
}
else
{
app_timer_cnt_diff_compute(app_timer_cnt_get(), tick, &diff);
}
}
return 0;
}
//計算高電平時長
static int calc_pulse_width(uint32_t *pw)
{
int ret;
uint32_t diff;
uint32_t tick;
ret = wait_pin_state(1, 5);
if(!ret)
{
return 0;
}
tick = app_timer_cnt_get();
ret = wait_pin_state(0, 5);
if(!ret)
{
return 0;
}
app_timer_cnt_diff_compute(app_timer_cnt_get(), tick, &diff);
*pw = diff;
return 1;
}
//等待高電平結束
static int wait_for_idle(void)
{
return wait_pin_state(0, 5);
}
//等待ADC採樣開始
//ADC採樣有2.5ms高電平
static int wait_for_adc(void)
{
int i;
int ret;
uint32_t pw;
for(i=0; i<32; i++)
{
ret = calc_pulse_width(&pw);
if(!ret)
{
return 0;
}
if(pw > 30)
{
return 1;
}
}
return 0;
}
//接收一位數據
static int recv_one_bit(int *bit)
{
int ret;
uint32_t t1;
uint32_t t2;
ret = calc_pulse_width(&t1);
if(!ret)
{
return 0;
}
ret = calc_pulse_width(&t2);
if(!ret)
{
return 0;
}
return decode_one_bit(t1, t2, bit);
}
//接收採樣起始信號
static int recv_start_bit(void)
{
int ret;
int bit;
ret = recv_one_bit(&bit);
if(!ret)
{
return 0;
}
return bit == -1;
}
//接收採樣數據
//數據=1位符號+13位數據
static int recv_adc_data(int *data)
{
int i;
int bit;
int ret;
int16_t tmp;
tmp = 0;
for(i=0; i<14; i++)
{
ret = recv_one_bit(&bit);
if(!ret)
{
LOG("drv_hm3000: recv bit%d failed!\r\n", i);
return 0;
}
if(bit < 0)
{
LOG("drv_hm3000: recv bit%d invalid!\r\n", i);
return 0;
}
if(bit)
{
tmp |= 1 << (15 - i);
}
}
//LOG("drv_hm3000: raw=%d\r\n", tmp);
*data = tmp >> 2;
return 1;
}
//採樣數據
static int sample_data(int *data)
{
int ret;
ret = wait_for_idle();
if(!ret)
{
LOG("drv_hm3000: wait idle timeout!\r\n");
return 0;
}
ret = wait_for_adc();
if(!ret)
{
LOG("drv_hm3000: wait adc timeout!\r\n");
return 0;
}
ret = recv_start_bit();
if(!ret)
{
LOG("drv_hm3000: recv start failed!\r\n");
return 0;
}
ret = recv_adc_data(data);
if(!ret)
{
LOG("drv_hm3000: recv data failed!\r\n");
return 0;
}
return ret;
}
#if 0
//調試數據
static void dump_data(void)
{
int i;
static uint32_t time[100];
for(i=0; i<64; i++)
{
calc_pulse_width(&time[i]);
}
for(i=0; i<64; i++)
{
LOG("%d,", time[i]);
}
LOG("\r\n");
}
#endif
//讀取採樣數據
int drv_hm3000_read(int *data)
{
//dump_data();
//return 0;
int ret;
//uint8_t sr;
//sd_nvic_critical_region_enter(&sr);
ret = sample_data(data);
//sd_nvic_critical_region_exit(sr);
return ret;
}
//打開芯片電源
void drv_hm3000_enable(void)
{
nrf_gpio_pin_write(HM3000_PIN_PWR, 1);
}
//關閉芯片電源
void drv_hm3000_disable(void)
{
nrf_gpio_pin_write(HM3000_PIN_PWR, 0);
}
//初始化驅動
void drv_hm3000_init(void)
{
nrf_gpio_cfg_output(HM3000_PIN_PWR);
nrf_gpio_cfg_input(HM3000_PIN_DATA, NRF_GPIO_PIN_NOPULL);
nrf_gpio_pin_write(HM3000_PIN_PWR, 0);
}