Hi all,
About my project:
I'm designing a Health and monitoring system using the Arduino MKR Zero. The project consists of a 4 Pin PWM Fan, 2x DS18B20 temperature sensors and an LCD. The fan I'm using is rated at 25kHz, so I discovered a bit of code online to modify the SAMD21 to suit and can manually set the Duty Cycle. The OneWire is working sufficiently and I'm reading addresses and temperatures back on the Serial Monitor currently (with plans to integrate LCD next).
I'm trying to work out how to use the temperature measured in real time, by the DS18B20, to set the Duty Cycle of the fan. I created 5 different integers with different duty cycles. The plan was to do an if statement.
However I tried unsuccessfully and need some advice/help. Code attached.
HUMS_17_12_2020.ino (6 KB)
2 sensors and 1 fan ?
If you can get a reading from a sensor you can use the map() function to convert it to a value in the correct range to write to the fan
Please turn off the WYSIWYG editor option and post your code agaian as with it turned on the forum software mangles code in code tags as you can see.
The problem has been reported and a fix is awaited
Currently, I'm using 2 Sensors and 1 Fan (Depending on air flow once implemented). Code attached, thanks.
#include <DallasTemperature.h>
#include <OneWire.h>
OneWire ds(1); // on pin 10 (a 4.7K resistor is necessary)
int PWMValue1 = 1919; //0% Duty Cycle
int PWMValue2 = 1500; //25% Duty Cycle
int PWMValue3 = 1000; //50% Duty Cycle
int PWMValue4 = 500; //75% Duty Cycle
int PWMValue5 = 0; //100% Duty Cycle
void setup()
// Output 25kHz PWM on digital pin D6 using timer TCC0 (10-bit resolution)
{
{
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide the 48MHz clock source by divisor 1: 48MHz/1=48MHz
GCLK_GENDIV_ID(4); // Select Generic Clock (GCLK) 4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK4
GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source
GCLK_GENCTRL_ID(4); // Select GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Enable the port multiplexer for the TCC0 PWM channel 2 (digital pin D6), SAMD21 pin PA20
PORT->Group[g_APinDescription[6].ulPort].PINCFG[g_APinDescription[6].ulPin].bit.PMUXEN = 1;
// Connect the TCC0 timer to the port outputs - port pins are paired odd PMUO and even PMUXE
// F & E specify the timers: TCC0, TCC1 and TCC2
PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg |= /*PORT_PMUX_PMUXO_F |*/ PORT_PMUX_PMUXE_F;
// Feed GCLK4 to TCC0 and TCC1
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable GCLK4 to TCC0 and TCC1
GCLK_CLKCTRL_GEN_GCLK4 | // Select GCLK4
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK4 to TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Normal (single slope) PWM operation: timer countinuouslys count up to PER register value and then is reset to 0
REG_TCC0_WAVE |= TCC_WAVE_WAVEGEN_NPWM; // Setup single slope PWM on TCC0
while (TCC0->SYNCBUSY.bit.WAVE); // Wait for synchronization
// Each timer counts up to a maximum or TOP value set by the PER (period) register,
// this determines the frequency of the PWM operation:
// 1919 = 25kHz
REG_TCC0_PER = 1919; // Set the frequency of the PWM on TCC0 to 25kHz
while(TCC0->SYNCBUSY.bit.PER);
/* The CCBx register value determines the duty cycle
REG_TCC0_CCB2 = PWMValue2; // TCC0 CCB2 - 50% duty cycle on D6
while(TCC0->SYNCBUSY.bit.CCB2);
// Divide the 48MHz signal by 1 giving 48MHz (20.8ns) TCC0 timer tick and enable the outputs
REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 | // Divide GCLK4 by 1
TCC_CTRLA_ENABLE; // Enable the TCC0 output
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization*/
}
{
Serial.begin(9600);
}
}
void loop()
{
{
byte i;
byte present = 0;
byte type_s;
byte data[12];
byte addr[8];
float celsius, fahrenheit;
if ( !ds.search(addr)) {
Serial.println("No more addresses.");
Serial.println();
ds.reset_search();
delay(250);
return;
}
Serial.print("ROM =");
for( i = 0; i < 8; i++) {
Serial.write(' ');
Serial.print(addr[i], HEX);
}
if (OneWire::crc8(addr, 7) != addr[7]) {
Serial.println("CRC is not valid!");
return;
}
Serial.println();
//the first ROM byte indicates which chip
switch (addr[0]) {
case 0x10:
Serial.println(" Chip = DS18S20"); // or old DS1820
type_s = 1;
break;
case 0x28:
Serial.println(" Chip = DS18B20");
type_s = 0;
break;
case 0x22:
Serial.println(" Chip = DS1822");
type_s = 0;
break;
default:
Serial.println("Device is not a DS18x20 family device.");
return;
}
ds.reset();
ds.select(addr);
ds.write(0x44, 1); // start conversion, with parasite power on at the end
delay(1000); // maybe 750ms is enough, maybe not
// we might do a ds.depower() here, but the reset will take care of it.
present = ds.reset();
ds.select(addr);
ds.write(0xBE); // Read Scratchpad
Serial.print(" Data = ");
Serial.print(present, HEX);
Serial.print(" ");
for ( i = 0; i < 9; i++) { // we need 9 bytes
data[i] = ds.read();
Serial.print(data[i], HEX);
Serial.print(" ");
}
Serial.print(" CRC=");
Serial.print(OneWire::crc8(data, 8), HEX);
Serial.println();
/*Convert the data to actual temperature
because the result is a 16 bit signed integer, it should
be stored to an "int16_t" type, which is always 16 bits
even when compiled on a 32 bit processor.*/
int16_t raw = (data[1] << 8) | data[0];
if (type_s) {
raw = raw << 3; // 9 bit resolution default
if (data[7] == 0x10) {
// "count remain" gives full 12 bit resolution
raw = (raw & 0xFFF0) + 12 - data[6];
}
} else {
byte cfg = (data[4] & 0x60);
// at lower res, the low bits are undefined, so let's zero them
if (cfg == 0x00) raw = raw & ~7; // 9 bit resolution, 93.75 ms
else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
//// default is 12 bit resolution, 750 ms conversion time
}
celsius = (float)raw / 16.0;
fahrenheit = celsius * 1.8 + 32.0;
Serial.print(" Temperature = ");
Serial.print(celsius);
Serial.print(" Celsius, ");
Serial.print(fahrenheit);
Serial.println(" Fahrenheit");
}
{
if (celsius < 25) {
// The CCBx register value determines the duty cycle
(REG_TCC0_CCB2 = PWMValue2); // TCC0 CCB2 - 50% duty cycle on D6
}
{
while(TCC0->SYNCBUSY.bit.CCB2);
}
// Divide the 48MHz signal by 1 giving 48MHz (20.8ns) TCC0 timer tick and enable the outputs
REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 | // Divide GCLK4 by 1
TCC_CTRLA_ENABLE; // Enable the TCC0 output
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
}
'UKHeliBob'
Okay, what your saying is I can use the map() function to translate the Celsius range to the RPM values. Once I've mapped the ranges how would I implement that into the statement 'REG_TCC0_CCB2 = 1919;'? Would I need to use the map() function in the place of '1919'? Apologies if I confuse you.
// The CCBx register value determines the duty cycle
REG_TCC0_CCB2 = 1919; // TCC0 CCB2 - 50% duty cycle on D6
while(TCC0->SYNCBUSY.bit.CCB2);
// Divide the 48MHz signal by 1 giving 48MHz (20.8ns) TCC0 timer tick and enable the outputs
REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 | // Divide GCLK4 by 1
TCC_CTRLA_ENABLE; // Enable the TCC0 output
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
The map() function allows you to convert a number in one range to the equivalent number in another range. The simple way to use it is to get the first number into a variable, map() it to to the second range and put the result in another variable then use that variable in the program
UKHeliBob
Awesome, just got back to this project. I implemented the Map function and the fan is ramping to the temperature on one DS18B20, however I need to create an average from 2 Ds18B20 devices first, then ramp to this variable. I assume it be a case of finding the addresses for each then using some simple math e.g. Ds18B20one + Ds18B20two / 2.
- How would I declare the one_wire devices to there addresses in setup?
Thankyou Kindly
Connor
#include <Wire.h>
#include <hd44780.h>
#include <hd44780ioClass/hd44780_I2Clcd.h> // i2c LCD i/o class header
#include <DallasTemperature.h>
#include <OneWire.h>
// declare the lcd object for auto i2c address location
hd44780_I2Clcd lcd;
// LCD geometry
const int LCD_COLS = 20;
const int LCD_ROWS = 4;
OneWire ds(1); // on pin 10 (a 4.7K resistor is necessary)
int TempPWMConv;
void setup()
// Output 25kHz PWM on digital pin D6 using timer TCC0 (10-bit resolution)
{
{
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide the 48MHz clock source by divisor 1: 48MHz/1=48MHz
GCLK_GENDIV_ID(4); // Select Generic Clock (GCLK) 4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK4
GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source
GCLK_GENCTRL_ID(4); // Select GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Enable the port multiplexer for the TCC0 PWM channel 2 (digital pin D6), SAMD21 pin PA20
PORT->Group[g_APinDescription[6].ulPort].PINCFG[g_APinDescription[6].ulPin].bit.PMUXEN = 1;
// Connect the TCC0 timer to the port outputs - port pins are paired odd PMUO and even PMUXE
// F & E specify the timers: TCC0, TCC1 and TCC2
PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg |= /*PORT_PMUX_PMUXO_F |*/ PORT_PMUX_PMUXE_F;
// Feed GCLK4 to TCC0 and TCC1
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable GCLK4 to TCC0 and TCC1
GCLK_CLKCTRL_GEN_GCLK4 | // Select GCLK4
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK4 to TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Normal (single slope) PWM operation: timer countinuouslys count up to PER register value and then is reset to 0
REG_TCC0_WAVE |= TCC_WAVE_WAVEGEN_NPWM; // Setup single slope PWM on TCC0
while (TCC0->SYNCBUSY.bit.WAVE); // Wait for synchronization
// Each timer counts up to a maximum or TOP value set by the PER (period) register,
// this determines the frequency of the PWM operation:
// 1919 = 25kHz
REG_TCC0_PER = 1919; // Set the frequency of the PWM on TCC0 to 25kHz
while(TCC0->SYNCBUSY.bit.PER);
/* register value determines the duty cycle
REG_TCC0_CCB2 = 1919; // TCC0 CCB2 - 50% duty cycle on D6
while(TCC0->SYNCBUSY.bit.CCB2);
// Divide the 48MHz signal by 1 giving 48MHz (20.8ns) TCC0 timer tick and enable the outputs
REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 | // Divide GCLK4 by 1
TCC_CTRLA_ENABLE; // Enable the TCC0 output
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization */
}
//Display Headings on LCD Display
{
pinMode(10, OUTPUT); // sets the digital pin 10 as output
digitalWrite(10, HIGH); // sets the digital pin 10 on
int status;
status = lcd.begin(LCD_COLS, LCD_ROWS);
if(status) // non zero status means it was unsuccesful
{
hd44780::fatalError(status); // does not return
}
// Print a message to the LCD
status = lcd.setCursor(0, 0);
lcd.print("Status:");
status = lcd.setCursor(0, 1);
lcd.print("Temperature:");
status = lcd.setCursor(0, 2);
lcd.print("Voltages:");
status = lcd.setCursor(0, 3);
lcd.print("Message:");
}
{
Serial.begin(9600);
}
}
void loop()
{
byte i;
byte present = 0;
byte type_s;
byte data[12];
byte addr[8];
float celsius, fahrenheit;
if ( !ds.search(addr)) {
Serial.println("No more addresses.");
Serial.println();
ds.reset_search();
delay(250);
return;
}
Serial.print("ROM =");
for( i = 0; i < 8; i++) {
Serial.write(' ');
Serial.print(addr[i], HEX);
}
if (OneWire::crc8(addr, 7) != addr[7]) {
Serial.println("CRC is not valid!");
return;
}
Serial.println();
//the first ROM byte indicates which chip
switch (addr[0]) {
case 0x10:
Serial.println(" Chip = DS18S20"); // or old DS1820
type_s = 1;
break;
case 0x28:
Serial.println(" Chip = DS18B20");
type_s = 0;
break;
default:
Serial.println("Device is not a DS18x20 family device.");
return;
}
ds.reset();
ds.select(addr);
ds.write(0x44, 1); // start conversion, with parasite power on at the end
delay(1000); // maybe 750ms is enough, maybe not
// we might do a ds.depower() here, but the reset will take care of it.
present = ds.reset();
ds.select(addr);
ds.write(0xBE); // Read Scratchpad
Serial.print(" Data = ");
Serial.print(present, HEX);
Serial.print(" ");
for ( i = 0; i < 9; i++) { // we need 9 bytes
data[i] = ds.read();
Serial.print(data[i], HEX);
Serial.print(" ");
}
Serial.print(" CRC=");
Serial.print(OneWire::crc8(data, 8), HEX);
Serial.println();
/*Convert the data to actual temperature
because the result is a 16 bit signed integer, it should
be stored to an "int16_t" type, which is always 16 bits
even when compiled on a 32 bit processor.*/
int16_t raw = (data[1] << 8) | data[0];
if (type_s) {
raw = raw << 3; // 9 bit resolution default
if (data[7] == 0x10) {
// "count remain" gives full 12 bit resolution
raw = (raw & 0xFFF0) + 12 - data[6];
}
} else {
byte cfg = (data[4] & 0x60);
// at lower res, the low bits are undefined, so let's zero them
if (cfg == 0x00) raw = raw & ~7; // 9 bit resolution, 93.75 ms
else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
//// default is 12 bit resolution, 750 ms conversion time
}
celsius = (float)raw / 16.0;
Serial.print(" Temperature = ");
Serial.print(celsius);
Serial.print(" Celsius, ");
{
TempPWMConv = map(celsius, 20, 60, 1919, 0); //Converting temperature to fan PWM value
}
{
// The CCBx register value determines the duty cycle
REG_TCC0_CCB2 = TempPWMConv; // TCC0 CCB2 - 50% duty cycle on D6
while(TCC0->SYNCBUSY.bit.CCB2);
// Divide the 48MHz signal by 1 giving 48MHz (20.8ns) TCC0 timer tick and enable the outputs
REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 | // Divide GCLK4 by 1
TCC_CTRLA_ENABLE; // Enable the TCC0 output
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
}