mirror of
https://github.com/eddyem/stm32samples.git
synced 2025-12-06 18:55:13 +03:00
497 lines
15 KiB
C
497 lines
15 KiB
C
/*
|
|
* This file is part of the DS18 project.
|
|
* Copyright 2021 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "ds18.h"
|
|
#include "proto.h"
|
|
#include "usb.h"
|
|
|
|
//#define EBUG
|
|
// ID1: 0x28 0xba 0xc7 0xea 0x05 0x00 0x00 0xc2
|
|
// ID2: 0x28 0x68 0xc9 0xe9 0x05 0x00 0x00 0x42
|
|
// ID3: 0x28 0x23 0xef 0xe9 0x05 0x00 0x00 0xab
|
|
|
|
|
|
#ifdef EBUG
|
|
#define DBG(x) do{USB_send(x);}while(0)
|
|
#else
|
|
#define DBG(x)
|
|
#endif
|
|
|
|
/* TIMING */
|
|
// freq = 1MHz
|
|
// ARR values: 1000 for reset, 100 for data in/out
|
|
// CCR1 values: 500 for reset, 60 for sending 0 or reading, <15 for sending 1
|
|
// CCR2 values: >550 if there's devices on line (on reset), >12 (typ.15) - read 0, < 12 (typ.1) - read 1
|
|
#define RESET_LEN ((uint16_t)1000)
|
|
#define BIT_LEN ((uint16_t)100)
|
|
#define RESET_P ((uint16_t)500)
|
|
#define BIT_ONE_P ((uint16_t)10)
|
|
#define BIT_ZERO_P ((uint16_t)80)
|
|
#define BIT_READ_P ((uint16_t)5)
|
|
#define RESET_BARRIER ((uint16_t)550)
|
|
#define ONE_ZERO_BARRIER ((uint16_t)10)
|
|
|
|
// answer for polling: conversion done
|
|
#define CONVERSION_DONE (0xff)
|
|
|
|
/*
|
|
* thermometer commands
|
|
* send them with bus reset!
|
|
*/
|
|
// find devices
|
|
#define OW_SEARCH_ROM (0xf0)
|
|
// read device (when it is alone on the bus)
|
|
#define OW_READ_ROM (0x33)
|
|
// send device ID (after this command - 8 bytes of ID)
|
|
#define OW_MATCH_ROM (0x55)
|
|
// broadcast command
|
|
#define OW_SKIP_ROM (0xcc)
|
|
// find devices with critical conditions
|
|
#define OW_ALARM_SEARCH (0xec)
|
|
/*
|
|
* thermometer functions
|
|
* send them without bus reset!
|
|
*/
|
|
// start themperature reading
|
|
#define OW_CONVERT_T (0x44)
|
|
// write critical temperature to device's RAM
|
|
#define OW_SCRATCHPAD (0x4e)
|
|
// read whole device flash
|
|
#define OW_READ_SCRATCHPAD (0xbe)
|
|
// copy critical themperature from device's RAM to its EEPROM
|
|
#define OW_COPY_SCRATCHPAD (0x48)
|
|
// copy critical themperature from EEPROM to RAM (when power on this operation runs automatically)
|
|
#define OW_RECALL_E2 (0xb8)
|
|
// check whether there is devices wich power up from bus
|
|
#define OW_READ_POWER_SUPPLY (0xb4)
|
|
|
|
/*
|
|
* thermometer identificator is: 8bits CRC, 48bits serial, 8bits device code (10h)
|
|
* Critical temperatures is T_H and T_L
|
|
* T_L is lowest allowed temperature
|
|
* T_H is highest -//-
|
|
* format T_H and T_L: 1bit sigh + 7bits of data
|
|
*/
|
|
|
|
|
|
/*
|
|
* RAM register:
|
|
* 0 - themperature: 1 ADU == 0.5 degrC
|
|
* 1 - sign (0 - T>0 degrC ==> T=byte0; 1 - T<0 degrC ==> T=byte0-0xff+1)
|
|
* 2 - T_H
|
|
* 3 - T_L
|
|
* 4 - 0xff (reserved)
|
|
* 5 - 0xff (reserved)
|
|
* 6 - COUNT_REMAIN (0x0c)
|
|
* 7 - COUNT PER DEGR (0x10)
|
|
* 8 - CRC
|
|
*/
|
|
|
|
// max amount of bits (20bytes x 8bits)
|
|
#define NmeasurementMax (160)
|
|
// reset pulse length (ms+1..2)
|
|
#define DS18_RESETPULSE_LEN (3)
|
|
// max length of measurement (ms+1..2)
|
|
#define DS18_MEASUR_LEN (800)
|
|
// maximal received data bytes
|
|
#define IMAXCTR (10)
|
|
|
|
static uint8_t DS18ID[8] = {0}, matchID = 0;
|
|
|
|
static void (*ow_process_resdata)() = NULL; // what to do with received data
|
|
|
|
static DS18_state dsstate = DS18_SLEEP;
|
|
static uint32_t Tstart = 0; // time of start pulse
|
|
// sent data, + 1 zero for last
|
|
static uint8_t CC1array[NmeasurementMax];
|
|
static uint8_t totbytesctr = 0; // total (i/o) data bytes counter
|
|
static int cc1buff_ctr = 0;
|
|
// received data
|
|
static uint8_t CC2array[NmeasurementMax];
|
|
static uint8_t receivectr = 0; // data bytes amount to receive
|
|
// prepare buffers to sending
|
|
#define OW_reset_buffer() do{cc1buff_ctr = 0; receivectr = 0; totbytesctr = 0;}while(0)
|
|
|
|
// several devices on line
|
|
void DS18_setID(const uint8_t ID[8]){
|
|
uint32_t *O = (uint32_t*)DS18ID, *I = (uint32_t*)ID;
|
|
*O++ = *I++; *O = *I;
|
|
matchID = 1;
|
|
#ifdef EBUG
|
|
USB_send("DS18_setID: ");
|
|
for(int i = 0; i < 8; ++i){
|
|
USB_send(" 0x");
|
|
printhex(DS18ID[i]);
|
|
}
|
|
USB_send("\n");
|
|
#endif
|
|
}
|
|
// the only device on line
|
|
void DS18_clearID(){
|
|
matchID = 0;
|
|
}
|
|
|
|
/**
|
|
* add next data byte
|
|
* @param ow_byte - byte to convert
|
|
*/
|
|
static uint8_t OW_add_byte(uint8_t ow_byte){
|
|
uint8_t i, byte;
|
|
for(i = 0; i < 8; i++){
|
|
if(ow_byte & 0x01){
|
|
byte = BIT_ONE_P;
|
|
}else{
|
|
byte = BIT_ZERO_P;
|
|
}
|
|
if(cc1buff_ctr == NmeasurementMax){
|
|
DBG("Tim2 buffer overflow\n");
|
|
return 0; // avoid buffer overflow
|
|
}
|
|
CC1array[cc1buff_ctr++] = byte;
|
|
ow_byte >>= 1;
|
|
}
|
|
++totbytesctr;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Adds Nbytes bytes 0xff for reading sequence
|
|
*/
|
|
static uint8_t OW_add_read_seq(uint8_t Nbytes){
|
|
uint8_t i;
|
|
if(Nbytes == 0) return 0;
|
|
totbytesctr += Nbytes;
|
|
receivectr += Nbytes;
|
|
Nbytes *= 8; // 8 bits for each byte
|
|
for(i = 0; i < Nbytes; i++){
|
|
if(cc1buff_ctr == NmeasurementMax){
|
|
DBG("Tim2 buffer overflow\n");
|
|
return 0;
|
|
}
|
|
CC1array[cc1buff_ctr++] = BIT_READ_P;
|
|
}
|
|
DBG("add rseq, rctr="); DBG(i2str(receivectr)); DBG("\n");
|
|
return 1;
|
|
}
|
|
|
|
// read received data (not more than IMAXCTR bytes!)
|
|
static uint8_t *OW_readbuf(){
|
|
#ifdef EBUG
|
|
DBG("ctr1="); DBG(u2str(DMA1_Channel2->CNDTR)); DBG("\n");
|
|
DBG("ctr2="); DBG(u2str(DMA1_Channel3->CNDTR)); DBG("\n");
|
|
DBG("\nn\tCC1\tCC2\n");
|
|
for(int i = 0; i < cc1buff_ctr; ++i){
|
|
DBG(u2str(i)); DBG("\t"); DBG(u2str(CC1array[i]));
|
|
DBG("\t"); DBG(u2str(CC2array[i])); DBG("\n");
|
|
}
|
|
#endif
|
|
static uint8_t got[IMAXCTR];
|
|
uint8_t *gptr = got;
|
|
DBG("rctr="); DBG(i2str(receivectr)); DBG("\n");
|
|
if(receivectr > IMAXCTR || receivectr == 0) return NULL;
|
|
uint8_t startidx = (totbytesctr - receivectr) * 8;
|
|
for(int i = startidx; i < cc1buff_ctr;){
|
|
uint8_t byte = 0;
|
|
for(int j = 0; j < 8; ++j){
|
|
byte >>= 1;
|
|
if(CC2array[i++] < ONE_ZERO_BARRIER) byte |= 0x80;
|
|
}
|
|
*gptr++ = byte;
|
|
#ifdef EBUG
|
|
USB_send("byte: "); printhex(byte); USB_send("\n");
|
|
#endif
|
|
}
|
|
return got;
|
|
}
|
|
|
|
DS18_state DS18_getstate(){return dsstate;}
|
|
|
|
static void DS18_detect(){
|
|
DBG("DS18_detect()\n");
|
|
TIM1->ARR = RESET_LEN;
|
|
TIM1->CCR1 = RESET_P;
|
|
TIM1->CCR2 = 0; // after interrupt it should be RESET_P+Tpdhigh+Tpdlow (575..800ms)
|
|
TIM1->EGR = TIM_EGR_UG; // generate update to put CCR1 from buffer into register
|
|
TIM1->SR = 0;
|
|
// enable update IRQ
|
|
TIM1->DIER = TIM_DIER_UIE;
|
|
TIM1->CCR1 = 0; // next time level should be max
|
|
// PA8 as AF opendrain output
|
|
GPIOA->CRH = (GPIOA->CRH & ~(GPIO_CRH_MODE8 | GPIO_CRH_CNF8)) |
|
|
CRH(8, CNF_AFOD | MODE_FAST);
|
|
// turn TIM1 on in OPM mode
|
|
TIM1->CR1 = TIM_CR1_CEN | TIM_CR1_OPM | TIM_CR1_URS;
|
|
dsstate = DS18_DETECTING;
|
|
}
|
|
|
|
static void DS18_getReg(int n){
|
|
uint8_t *r = OW_readbuf();
|
|
if(!r || receivectr != n){
|
|
DBG("Bad read value!\n");
|
|
return;
|
|
}
|
|
printsp(r, n);
|
|
}
|
|
|
|
static int chkCRC(const uint8_t data[9]){
|
|
uint8_t crc = 0;
|
|
for(int n = 0; n < 8; ++n){
|
|
crc = crc ^ data[n];
|
|
for(int i = 0; i < 8; i++){
|
|
if(crc & 1)
|
|
crc = (crc >> 1) ^ 0x8C;
|
|
else
|
|
crc >>= 1;
|
|
}
|
|
}
|
|
//USB_send("got crc: "); printhex(crc); USB_send("\n");
|
|
if(data[8] == crc) return 0;
|
|
return 1;
|
|
}
|
|
|
|
// data processing functions
|
|
static void DS18_gettemp(){ // calculate T
|
|
dsstate = DS18_SLEEP;
|
|
#ifdef EBUG
|
|
for(int i = 0; i < NmeasurementMax; ++i){
|
|
DBG(u2str(i)); DBG("\t"); DBG(u2str(CC2array[i])); DBG("\n");
|
|
}
|
|
#endif
|
|
uint8_t *r = OW_readbuf();
|
|
if(!r || receivectr != 9){
|
|
DBG("Bad count");
|
|
dsstate = DS18_ERROR;
|
|
return;
|
|
}
|
|
int ctr;
|
|
for(ctr = 0; ctr < 9; ++ctr){
|
|
if(r[ctr] != 0xff) break;
|
|
}
|
|
if(ctr == 9){
|
|
USB_send("Target ID not found");
|
|
return;
|
|
}
|
|
if(chkCRC(r)){
|
|
USB_send("CRC is wrong\n");
|
|
return;
|
|
}
|
|
uint16_t l = r[0], m = r[1], v;
|
|
int32_t t;
|
|
if(r[4] == 0xff){ // DS18S20
|
|
t = ((uint32_t)l) * 10;
|
|
t >>= 1;
|
|
}else{ // DS18B20
|
|
v = ((m & 7) << 8)| l;
|
|
t = ((uint32_t)v)*10;
|
|
t >>= 4;
|
|
}
|
|
if(m & 0x80) t = -t;
|
|
printT(t);
|
|
}
|
|
static void DS18_pollt(){ // poll T
|
|
uint8_t *r;
|
|
if((r = OW_readbuf()) && receivectr == 1){
|
|
if(*r == CONVERSION_DONE){
|
|
OW_reset_buffer();
|
|
if(matchID){ // add MATCH command & target ID
|
|
OW_add_byte(OW_MATCH_ROM);
|
|
for(int i = 0; i < 8; ++i)
|
|
OW_add_byte(DS18ID[i]);
|
|
}else{
|
|
OW_add_byte(OW_SKIP_ROM);
|
|
}
|
|
OW_add_byte(OW_READ_SCRATCHPAD);
|
|
OW_add_read_seq(9);
|
|
ow_process_resdata = DS18_gettemp;
|
|
DS18_detect(); // reset
|
|
return;
|
|
}
|
|
}
|
|
OW_reset_buffer();
|
|
OW_add_read_seq(1); // send read seq waiting for end of conversion
|
|
ow_process_resdata = DS18_pollt;
|
|
dsstate = DS18_RDYTOSEND;
|
|
}
|
|
static void DS18_getID(){
|
|
DS18_getReg(8);
|
|
}
|
|
static void DS18_getSP(){
|
|
DS18_getReg(9);
|
|
}
|
|
|
|
// TIM1_CH1 & TIM1_CH2 are used for data sending & pulse length measurement
|
|
// T1ch1 - DMA1Ch2, T1ch2 - DMA1Ch3
|
|
void DS18_pinsetup(){
|
|
TIM1->CR1 = 0;
|
|
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_TIM1EN | RCC_APB2ENR_AFIOEN;
|
|
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
|
|
pin_set(GPIOA, 1<<8); // 1 @ line
|
|
// PA8 as opendrain output
|
|
GPIOA->CRH = (GPIOA->CRH & ~(GPIO_CRH_MODE8 | GPIO_CRH_CNF8)) |
|
|
CRH(8, CNF_ODOUTPUT | MODE_FAST);
|
|
TIM1->PSC = 71; // 1MHz
|
|
// CC1 is output (PWM mode 1, active->inactive, enable preload), CC2 is input @TI1
|
|
TIM1->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE |
|
|
TIM_CCMR1_CC2S_1;
|
|
// enable CC1 & CC2, CC1 active low
|
|
TIM1->CCER = TIM_CCER_CC1E | TIM_CCER_CC2E | TIM_CCER_CC1P;
|
|
TIM1->DIER = 0; // disable IRQ & DMA events
|
|
// main output enable
|
|
TIM1->BDTR = TIM_BDTR_MOE;
|
|
// T1Ch2: per->mem, T1Ch1: mem->per; 16bit->8bit,
|
|
DMA1_Channel2->CCR = DMA_CCR_MINC | DMA_CCR_PSIZE_0 | DMA_CCR_TCIE | DMA_CCR_DIR;
|
|
DMA1_Channel3->CCR = DMA_CCR_MINC | DMA_CCR_PSIZE_0 | DMA_CCR_TCIE;
|
|
DMA1_Channel2->CPAR = (uint32_t)&TIM1->CCR1;
|
|
DMA1_Channel3->CPAR = (uint32_t)&TIM1->CCR2;
|
|
TIM1->CR1 = TIM_CR1_URS; // only ARR overflow generates interrupt
|
|
|
|
NVIC_EnableIRQ(TIM1_UP_IRQn); // last interrupt to turn timer off
|
|
NVIC_EnableIRQ(DMA1_Channel2_IRQn);
|
|
dsstate = DS18_SLEEP;
|
|
}
|
|
|
|
// start measurement
|
|
static void DS18_startmeas(uint32_t Tms){
|
|
DBG("startmeas()\n");
|
|
dsstate = DS18_READING;
|
|
Tstart = Tms;
|
|
DMA1->IFCR = DMA_IFCR_CGIF1 | DMA_IFCR_CGIF2; // clear all flags
|
|
DMA1_Channel2->CMAR = (uint32_t)CC1array;
|
|
DMA1_Channel3->CMAR = (uint32_t)CC2array;
|
|
DMA1_Channel2->CNDTR = cc1buff_ctr+1; // +1 for last works
|
|
DMA1_Channel3->CNDTR = cc1buff_ctr;
|
|
// enable both DMA channels
|
|
DMA1_Channel2->CCR |= DMA_CCR_EN;
|
|
DMA1_Channel3->CCR |= DMA_CCR_EN;
|
|
// enable DMA events generation
|
|
TIM1->DIER = TIM_DIER_CC1DE | TIM_DIER_CC2DE;
|
|
TIM1->ARR = BIT_LEN;
|
|
TIM1->SR = 0;
|
|
GPIOA->CRH = (GPIOA->CRH & ~(GPIO_CRH_MODE8 | GPIO_CRH_CNF8)) |
|
|
CRH(8, CNF_AFOD | MODE_FAST);
|
|
// turn TIM1 on
|
|
TIM1->CR1 = TIM_CR1_CEN | TIM_CR1_URS;
|
|
}
|
|
|
|
|
|
static void DS18_stopmeas(){
|
|
DBG("stopmeas()\n");
|
|
// stop timer
|
|
TIM1->CR1 = 0;
|
|
// disable DMA channel
|
|
DMA1_Channel2->CCR &= ~DMA_CCR_EN;
|
|
DMA1_Channel3->CCR &= ~DMA_CCR_EN;
|
|
#ifdef EBUG
|
|
DBG("ctr="); DBG(u2str(DMA1_Channel2->CNDTR)); DBG("\nn\tCC1\n");
|
|
int nmax = NmeasurementMax - DMA1_Channel2->CNDTR;
|
|
for(int i = 0; i < nmax; ++i){
|
|
DBG(u2str(i)); DBG("\t"); DBG(u2str(CC1array[i])); DBG("\n");
|
|
}
|
|
#endif
|
|
dsstate = DS18_ERROR;
|
|
}
|
|
|
|
// processing, Tms - current time in milliseconds
|
|
void DS18_process(uint32_t Tms){
|
|
switch(dsstate){
|
|
case DS18_DETDONE:
|
|
DBG("TIM1->CCR2="); DBG(u2str(TIM1->CCR2)); DBG("\n");
|
|
/* DBG("SR="); DBG(u2str(TIM1->SR)); DBG("\n");
|
|
if(TIM1->SR & TIM_SR_CC2OF){
|
|
TIM1->SR = 0; DBG("Overcapture!\n");
|
|
}*/
|
|
if(TIM1->CCR2 > RESET_BARRIER) DS18_startmeas(Tms);
|
|
else dsstate = DS18_ERROR;
|
|
break;
|
|
case DS18_RDYTOSEND: // send data without reset pulse
|
|
DS18_startmeas(Tms);
|
|
break;
|
|
case DS18_READING:
|
|
if(Tms - Tstart > DS18_MEASUR_LEN)
|
|
DS18_stopmeas();
|
|
break;
|
|
case DS18_GOTANSWER:
|
|
// disable DMA channels
|
|
DMA1_Channel2->CCR &= ~DMA_CCR_EN;
|
|
DMA1_Channel3->CCR &= ~DMA_CCR_EN;
|
|
dsstate = DS18_SLEEP;
|
|
if(ow_process_resdata) ow_process_resdata();
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
int DS18_start(){
|
|
if(dsstate != DS18_SLEEP && dsstate != DS18_ERROR) return 0;
|
|
OW_reset_buffer();
|
|
OW_add_byte(OW_SKIP_ROM);
|
|
OW_add_byte(OW_CONVERT_T);
|
|
OW_add_read_seq(1); // send read seq waiting for end of conversion
|
|
DBG("start()\n");
|
|
ow_process_resdata = DS18_pollt; // after data will be done calculate T and show it
|
|
DS18_detect();
|
|
return 1;
|
|
}
|
|
|
|
int DS18_readID(){
|
|
if(dsstate != DS18_SLEEP && dsstate != DS18_ERROR) return 0;
|
|
OW_reset_buffer();
|
|
OW_add_byte(OW_READ_ROM);
|
|
OW_add_read_seq(8); // wait for 8 bytes
|
|
ow_process_resdata = DS18_getID;
|
|
DBG("readID()\n");
|
|
DS18_detect();
|
|
return 1;
|
|
}
|
|
|
|
int DS18_readScratchpad(){
|
|
if(dsstate != DS18_SLEEP && dsstate != DS18_ERROR) return 0;
|
|
OW_reset_buffer();
|
|
OW_add_byte(OW_SKIP_ROM);
|
|
OW_add_byte(OW_READ_SCRATCHPAD);
|
|
OW_add_read_seq(9);
|
|
ow_process_resdata = DS18_getSP;
|
|
DBG("readSP()\n");
|
|
DS18_detect();
|
|
return 1;
|
|
}
|
|
|
|
void dma1_channel2_isr(){ // wait for last data bit
|
|
DMA1->IFCR = DMA_IFCR_CGIF2;
|
|
TIM1->CR1 |= TIM_CR1_OPM;
|
|
TIM1->CCR1 = 0; // don't go to zero
|
|
TIM1->SR &= ~TIM_SR_UIF; // clear update flag (or interrupt will run just now)
|
|
TIM1->DIER |= TIM_DIER_UIE;
|
|
}
|
|
|
|
// update IRQ
|
|
void tim1_up_isr(){
|
|
TIM1->SR = 0; // clear all flags (and overcapture too!) &= ~TIM_SR_UIF;
|
|
TIM1->CR1 = 0;
|
|
if(dsstate == DS18_DETECTING) dsstate = DS18_DETDONE;
|
|
else if(dsstate == DS18_READING) dsstate = DS18_GOTANSWER;
|
|
}
|
|
|
|
void DS18_poll(){
|
|
OW_reset_buffer();
|
|
OW_add_read_seq(1); // send read seq waiting for end of conversion
|
|
ow_process_resdata = DS18_pollt;
|
|
dsstate = DS18_RDYTOSEND;
|
|
}
|