Edward Emelianov b6075b6538 add DS18
2021-03-02 22:51:07 +03:00

439 lines
13 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"
//#define EBUG
#ifdef EBUG
#include "usb.h"
#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 (1bytes x 8bits)
#define NmeasurementMax (88)
// 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 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)
/**
* 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);
}
// 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;
}
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();
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;
}