I thought I’d drop this here for someone who needed a copy-paste solution.
I am parsing a serial string output from a YDLIDAR X4 rotating lidar. I have to do this because the library sucks. a lot. I mean I’m not over my heels with the unit anyhow, first off who the hell outputs at 128,000 baud? I’ll post the code when it’s done enough for someone to build off of it.
I was getting an error in number of bytes in the packet as parsed from the serial string so I had to break out the function into really busy but obvious code to see where I was going wrong. The code has me concatenating two bytes and an upper nibble. Ignore the discrepancy in how I added my bytes backwards in my code vs. how the device actually stacks it’s data… because you know: WHO IN THEIR RIGHT MIND SHIFTS LSB ON THE LEFT?! (note the “length bytes as shown in 12 bits and the “response length” example below.)
Anyhow… feel free to show me how to do it better in the comments. Obviously this is built for you to look at it do it’s magic in a serial monitor.
What a groaner.. who needs another datalogger? Well I do, so stuff it. I was inspired to check out these iButtonLink devices that allow you to daisy-chain connect a number of devices in a single circuit of sorts with RJ45 patch cables. I had seen these a couple years ago in some top-notch datacenters that terminated to a concentrator (4 strings) that were then pushed up to some IBM services app server.
A while back I published a Microsoft PIC microcontroller version of this … I decided to give it another whirl with the “easy button” … Arduino. Of course there were a couple libraries that I just had to collectively stuff into the same sketch and easy-peasy it was done in an hour or so.
This time I have my data stored to an SD card, which … I hastily put together.
If I was actually going to finish this I would add three things to this to finish it up.
Use an RTC, figure an interface to enter the correct time. (maybe add an ethernet shield and get NTP?) … or just manual enter.
I’d grab the interval time off the RTC and just have the loop do a non-blocking wait, then poll the RTC time or interrupt (whatever you favorite way to do this is..) that way I was collecting trend information on the “:00″s.
I would add a method of checking for an SD card to be interested and allow it to start logging from card insert. I would also add time/date stamp to the file and finally, still SD card related I would check file size and stop logging when I got close to full.
So that is that. I’ve been creating a lot of fun lately but nothing mind-bending.. just going through every module I’ve ever bought doing the demo code … or going the other way around.. working on every Arduino demo/example in a class .. I’ve also built an FE boat, moved my workshop inside (update video coming kind of soon) and been working on PopBot 0.5 ..
The code:
/*
* Chas Ihler
* @chasxmd
* https://iradan.com
*
* License: Public Domain - it's mostly copy and paste and preferences anyways.
*
* iButtonLink, grabs all sensors and logs them to card if present.
* Recommendations:
* Add an RTC and a method of updating time.
* I'd also grab actual seconds off the RTC for logging and use a non-blocking wait for the polling/logging.
*
* Libraries:
* https://github.com/matmunk/DS18B20
* IDE built in SD card/SPI library
*
* Sensor:
* https://www.ibuttonlink.com/products/t-sense-temperature-sensor
* which has this device within..
* https://datasheets.maximintegrated.com/en/ds/DS18B20.pdf
*
* Hookup:
* You'll need a pullup resistor from 5V to pin 2
* ibutton hookup:
* 1 - GND
* 2 - 5VDC
* 3 - NC
* 4 - Arduino Pin 2 (w/ Pullup)
* 5 - GND
* 6 - NC
* 7 - NC
* 8 - NC
*
* SD card attached to SPI bus as follows:
* MOSI - pin 11
* MISO - pin 12
* CLK - pin 13
* CS - pin 4
*
*/
#include <DS18B20.h>
#include <SPI.h>
#include <SD.h>
DS18B20 ds(2); //pin 2
const int chipSelect = 4;
void setup() {
Serial.begin(115200);
delay(100);
Serial.println("----------------------------------------");
Serial.println(" ");
Serial.println("DS18B20 / iButtonLink Demo");
Serial.print("Devices: ");
Serial.println(ds.getNumberOfDevices());
Serial.println(" ");
Serial.println("----------------------------------------");
Serial.print("Initializing SD card...");
// see if the card is present and can be initialized:
if (!SD.begin(chipSelect)) {
Serial.println("Card failed, or not present");
// don't do anything more:
while (1);
}
Serial.println("card initialized.");
}
void loop() {
int i_address;
Serial.println("----------------------------------------");
while (ds.selectNext()) {
String str_dataString = "";
switch (ds.getFamilyCode()) {
case MODEL_DS18S20:
Serial.println("Model: DS18S20/DS1820");
break;
case MODEL_DS1822:
Serial.println("Model: DS1822");
break;
case MODEL_DS18B20:
Serial.println("Model: DS18B20");
break;
default:
Serial.println("Unrecognized Device");
break;
}
uint8_t address[8];
ds.getAddress(address);
Serial.print("Address:");
for (uint8_t i = 0; i < 8; i++) {
Serial.print(" ");
Serial.print(address[i]);
str_dataString += String(address[i]);
}
Serial.println();
/*
Serial.print("Resolution: ");
Serial.println(ds.getResolution());
Serial.print("Power Mode: ");
if (ds.getPowerMode()) {
Serial.println("External");
} else {
Serial.println("Parasite");
}
*/
Serial.print("Temperature: ");
Serial.print(ds.getTempC());
Serial.print(" C / ");
Serial.print(ds.getTempF());
Serial.println(" F");
str_dataString += ",";
str_dataString += String(ds.getTempC());
File dataFile = SD.open("datalog.txt", FILE_WRITE);
if (dataFile) {
dataFile.println(str_dataString);
dataFile.close();
Serial.println(str_dataString);
} else {
Serial.println("error opening datalog.txt");
}
Serial.println(" ***** ");
Serial.println();
}
delay(30000);
}
Using a PIC Microcontroller to pull temperature of iButtonLink T-sense sensor.
I pulled this off the wayback machine because I lost this post in the great database loss of how ever many years ago. I’m re-posting because I’m working on an expanded version of this with an Arduino dev board and wanted a reminder of what I did five years ago. Originally posted 1/2/2015. No edits.
—————————————————————————————————————-
I came across a stash of iButton T-sense 1-wire sensors.. so I grabbed a couple and decided to check out 1-wire.
Maxim makes a 1-wire device called the DS18B20. It’s a 9-12 bit temperature sensor with the possibility of being powered by parasitic power from the data line. This cuts the signal path down to a single DQ line and a return. A company called iButtonLink produces a nice little wrapper around this device called a T-Sense. There are a couple pieces of software out there that will allow you to hook these up to monitoring systems, I don’t have any though. These devices come with a 64-bit address code and can be daisy-chained which makes having many of these devices monitored very nice.
At first I thought, ugh.. lame I have to send, and parse 64-bit codes in a little 8 bit micro.. doesn’t sound like a ton of fun for just fooling around on a day off.. thank fully they have a “Skip ROM” feature/command which works similar to a broadcast but can only be used when you have one device on the bus. If there is one thing left in this project I might consider finishing it’d be to add the addressing in and daisy-chain a few of these.
For my circuit I hooked up 5VDC (but later ran it on 3.3V just fine) and the 5VDC return on pins 1 & 2. Then the DQ link and return on pins 4&5. The signaling is interesting as the 1-wire bus needs a weak pull and works with an open collector circuits. The master starts all signaling, writes and reads. The 1’s and 0’s are based on how long the master or slave sinks the DQ line. To accomplish this in the PIC microcontroller I switched the port from an output to a three state input when I needed the port to be in weak-pull up mode (which is also hand when I need to sample the port for a response from a slave). The pull up on the resistor in my circuit is 10Kohm but I’ve seen 4.7KOhm and I’m sure anywhere in the neighborhood is fine. Finally if you do some digging you’ll notice I run this in low speed mode, if I remember correctly the “high speed” mode is 4x faster. I don’t think speed of data transfer is really relevant when you’re waiting for 750ms for a temperature conversation though.
I initially started with just determining if there was a 1-wire device on the bus. If you perform a “reset” (master sinks the bus low for 480us then releases to hi-z for 70us and then performed a sample.. any (all) device(s) will sink the line slow to prove their presence…then another 410us of delay before continuing. I got this one first try.. better luck than my first time with I2C! I then wrote the code (including sampling tidbits of Microchip AN1199 code to optimize) to do an actual temperature conversion and request it (by commanding a “read scratch pad”). The device dumps all 9 bytes of it’s registers. On that note I just remembered I should mention I did NOTHING with the CRC byte.. that’s all you if you care.
My temperature conversion code looks like this: (bus control m = master or PIC, s=slave or sensor)
m RESET
s PRESENCE flag
m SKIP ROM (0xCC)
m CONV TEMP (0x44)
m HOLD DQ (Hold line high 750ms for 12bit conversion .. I am guessing we hold it high for the parasitic power supply)
m RESET
s PRESENCE flag
m SKIP ROM (0xCC)
m READ SCRATCHPAD (0xBE)
s 9 Bytes of Data
The 12-bit conversion is basically 0.0625 deg C for every bit from 0C, The LSB holds a temperature sign.
The output of my program looks like..
There are tons of details on what a “1” is an what a “0” is, the ROM code (READ ROM, MATCH ROM), changing the configuration to 9bit for 94 ms conversions over 12bit 750ms conversions. This is all stuff you can grab out of the DS18B20 specification sheet or AN1199.
I hope you get some use out of this.. I still have enough of these T-Sense modules.. maybe if someone really wants one I’ll drop it in the mail… or perhaps we can set up a big box of electronics to ship around and I can dump some in there.
I got a rando e-mail offering me “free parts” and I usually don’t even bother opening the e-mail. It’s ALWAYS garbage right? Well, maybe I hadn’t slept well the night before but I opened it. (psss. You can scroll down about a page before I actually get anywhere in this post…)
What was I thinking? I hovered carefully over the link… seemed pretty safe… short-ish domain ended with a .com .. not extra stuff on the end… OK. They got me. The website was http://digitspace.com — It seemed like an electronics website so I e-mailed. Back…. going back and forth they seem to be running some kind of promotion or perhaps they are trying to build some brand recognition getting bloggers to give them some advertising. In the end selected $50 worth of parts that consisted of two ESP32 micro dev boards that had WiFi (of course), LoRa, OLED display and and extra LED. There was some switch button types but they were a bit more and for some reason I didn’t think I needed them. Well a week later these things actually showed up (and I didn’t have to give them my credit card or paypal info!).
OK THE START..
A week later the boards showed up in the mail.. they look and feel reasonable and they came with some sample firmware that proved they worked. Unfortunately the site did not include any documentation… but I did the “hardwork” for you if you want to follow along… but let’s start with the end… So far I am here:
First thing first I had to add ESP32 support to my Arduino IDE.. I dropped this in the “additional board manager URLs”:
I found a simple tx-rx LoRa code off the internet but I won’t share because it took too much re-work to get working on my board. So I took it’s essience …
I had to add these libraries:
Add libraries: Adafruit SSD1306 by Adafruit Adafruit GFX Library by Adafruit LoRa by Sandeep Mistry
(all came right off the normal library manager)
I couldn’t find an OLED library that worked until reading some and found I liked this one that seemed to be the “best for me”: https://github.com/osresearch/esp32-ttgo
My findings were similar … though I haven’t ACCURATELY measured it my USB analyzer says 50mA on and 10mA off.. no peak readings yet but I can live with 10mA in my project.
By request one of my long lost blogs was a MODbus RTU application for a PIC 18F27J53. You could easily plop this onto many of the 18F series micros. It doesn’t do much but it’s a great stepping stone for throwing in your own IO. I’ll probably follow up with an Arduino version in a while.. I’ll put it on my “todo” list.
The code…
/*
File: main.c
Author: Charles M Ihler
Contact at: http://iradan.com
*
Created on November 8, 2014, 2:37 PM
*
Target Device:
18F27J53 Development Board by AtomSoft
*
Project: MODbus/RTU slave
*
Notes
We will ignore both query high bytes for now
TODO: Pull all hard coded IO out eventually and set it up via USB?
Maybe a terminal application for enabling points?
*
*
Version:
0.02 Controlled sends caned reply on request to address on RS-232
0.03 CRC works on RX
0.04 No actual IO yet..
Function 0x02 (Read Input Status) Started
0.05 Ported to 18F17J53 for expanded IO/USB options.
12MHz XTAL with CPUDIV set to 16MHz
*
/ //#ifndef _XTAL_FREQ //#define _XTAL_FREQ 16000000 // //#define __delay_us(x) _delay((unsigned long)((x)(_XTAL_FREQ/4000000.0)))
//#define __delay_ms(x) _delay((unsigned long)((x)*(_XTAL_FREQ/4000.0)))
//#endif
// PIC18F27J53 Configuration Bit Settings
// 'C' source line config statements
include
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
// CONFIG1L
pragma config WDTEN = OFF // Watchdog Timer (Disabled - Controlled by SWDTEN bit)
pragma config PLLDIV = 3 // PLL Prescaler Selection (Divide by 3 (12 MHz oscillator input))
pragma config CFGPLLEN = ON // PLL Enable Configuration Bit (PLL Enabled)
pragma config STVREN = ON // Stack Overflow/Underflow Reset (Enabled)
pragma config XINST = OFF // Extended Instruction Set (Enabled)
// CONFIG1H
pragma config CPUDIV = OSC3_PLL3// CPU System Clock Postscaler (CPU system clock divide by 3 from 48MHz)
pragma config CP0 = OFF // Code Protect (Program memory is not code-protected)
// CONFIG2L
pragma config OSC = HSPLL // Oscillator (HS+PLL, USB-HS+PLL)
pragma config SOSCSEL = HIGH // T1OSC/SOSC Power Selection Bits (High Power T1OSC/SOSC circuit selected)
pragma config CLKOEC = OFF // EC Clock Out Enable Bit (CLKO output enabled on the RA6 pin)
pragma config FCMEN = OFF // Fail-Safe Clock Monitor (Disabled)
pragma config IESO = OFF // Internal External Oscillator Switch Over Mode (Disabled)
define _XTAL_FREQ 16000000 //defined for delay
char ctxt[10], buf1, testcrc[4]; //buff serial string volatile unsigned int ping, isrcall, index, reading, new_rx; int address, crc_high, crc_low, i, querylength; unsigned int result; //these variables are for fuctions to be enabled/disabled and pushed into application code via later int frame, z, bufferbits, query_reg_address, y; char data_out[64]; char reg_address[255];
/*
*
Interrupt Service
*
UART RX and Timer 1
*
*/
void interrupt ISR() {
if (PIR1bits.RCIF) // see if interrupt caused by incoming data
{
isrcall = 0x01;
char temp;
temp = RCREG; // read the incoming data
buf1 = temp;
if(temp==address && reading==0) //if my address..
{
index = 0; //reset index
reading = 1; //from now on go to else if
LATCbits.LATC1 = 1;
new_rx = 0;
ping = 1;
}
else if(reading == 1) //in middle of GPS sentence
{
//TODO reset timeout timer
ctxt[index] = temp; //load it up
index++; //increment index
ping = 1; //this is for debugging
if(index > 6) //1+7 = master frame.
{
index = 0; //reset index
reading = 0; //no longer storing the string
new_rx = 1; //"ding"
T1CONbits.TMR1ON = 0;
}
}
}
//RCSTA2bits.FERR = 0; //Clear errors
//RCSTA2bits.OERR = 0;
//time out timer, if tripped new_rx=0;
if (PIR1bits.TMR1IF)
{
new_rx=0;
ping = 0;
T1CONbits.TMR1ON = 0;
PIR1bits.TMR1IF = 0;
if (reading) {
reading = 0;
LATCbits.LATC0 = 1;
}
}
}
void init_io(void) {
INTCONbits.GIE = 0; //no interruptions please ADCON0 = 0b00000000; //don't need any ADC ADCON1 = 0b00000000; //speed Vref=AVdd, VssRef=AVss LATA = 0x00; LATB = 0x00; LATC = 0x00; TRISAbits.TRISA0 = 1; // address 1 TRISAbits.TRISA1 = 1; // address 2 TRISAbits.TRISA2 = 1; // address 3 TRISAbits.TRISA3 = 0; // output TRISAbits.TRISA5 = 0; // output TRISBbits.TRISB0 = 0; // TRISBbits.TRISB1 = 1; // TRISBbits.TRISB2 = 0; // TRISBbits.TRISB3 = 0; // TRISBbits.TRISB4 = 0; // TRISBbits.TRISB5 = 1; // TRISBbits.TRISB6 = 0; // TRISBbits.TRISB7 = 0; // TRISCbits.TRISC0 = 0; // timer LED output TRISCbits.TRISC1 = 0; // output TRISCbits.TRISC2 = 0; // LED test output TRISCbits.TRISC3 = 0; // output //TRISCbits.TRISC4 = 1; // USB //TRISCbits.TRISC5 = 1; // USB TRISCbits.TRISC6 = 0; // output TX1 TRISCbits.TRISC7 = 1; // input RX1
}
void __delay_10ms(unsigned char n) //__delay functions built-in can't be used for much at this speed… so!
{
while (n-- != 0) {
__delay_ms(10);
}
}
void uart_xmit(unsigned int mydata_byte) {
while(!TXSTAbits.TRMT); // make sure buffer full bit is high before transmitting TXREG = mydata_byte; // transmit data
}
void write_uart(const char txt) { //this send a string to the TX buffer //one character at a time while(txt)
uart_xmit(*txt++);
}
void serial_init(void)
{
//9600 8N1
TXSTA1bits.SYNC = 0; TXSTA1bits.BRGH=1; // select low speed Baud Rate (see baud rate calcs below) TXSTA1bits.TX9=0; // select 8 data bits TXSTA1bits.TXEN = 1; // enable transmit RCSTA1bits.SPEN=1; // serial port is enabled RCSTA1bits.RX9=0; // select 8 data bits RCSTA1bits.CREN=1; // receive enabled SPBRG1=102; SPBRGH1=0; PIR1bits.RCIF=0; // make sure receive interrupt flag is clear PIE1bits.RCIE=1; // enable UART Receive interrupt INTCONbits.PEIE = 1; // Enable peripheral interrupt INTCONbits.GIE = 1; // enable global interrupt __delay_10ms(5); // give time for voltage levels on board to settle
}
void run_timer (void) {
T1CONbits.TMR1ON = 0;
TMR1=0;
TMR1L=0x00;
TMR1H=0xAA;
T1CONbits.TMR1CS = 0x01;
T1CONbits.T1CKPS = 0x01;
PIE1bits.TMR1IE = 1;
T1CONbits.TMR1ON = 1;
// PIR1bits.TMR1IF=0;
}
void check_my_address(void) {
/*
* Determine what address the unit is based on DIP switches
* PORTAbits.RA0 Switch 1
* PORTAbits.RA1 Switch 2
*/
if (PORTAbits.RA0 & PORTAbits.RA1) address = 0x04; if (!PORTAbits.RA0 & PORTAbits.RA1) address = 0x03; if (PORTAbits.RA0 & !PORTAbits.RA1) address = 0x02; if (!PORTAbits.RA0 & !PORTAbits.RA1) address = 0x01;
}
/*
TODO
Poll the inputs
*
I will probably use a 74138 to address banks with 74HC245 for byte/banks of inputs
I will likely use the same set up for "Coils"
For input registers (analog inputs) I will consider perhaps some kind of A/D input with CD4066+74HC138
*
*/
void poll_my_inputs(void) {
//just stuff it with something for now for (z = 0; z < 64; ++z) { reg_address[z] = z; }
}
/*
did the chicken come before the egg?
this MODbus CRC bit was gently borrowed from the internet..
I can't determine who wrote it.. it shows up in an Ardiuno Sketch for MODbus
Modbus over serial line - RTU Slave Arduino Sketch
with referenced of Juan Pablo Zometa, Samuel Marco, Andras Tucsni, Philip Costigan
It also shows up on a number of websites and forums uncredited… many PIC or C based ..
so who knows, but I didn't write it
The MODbus CRC-16 function is outlined on the Modicon site for reference
*/
unsigned int modbus_crc(unsigned char buf[], int start, int cnt)
{
int i,j;
unsigned temp, temp2, flag;
temp=0xFFFF;
for (i=start; i<cnt; i++){
temp=temp ^ buf[i];
for (j=1; j<=8; j++){ flag=temp & 0x0001; temp=temp >> 1;
if (flag) temp=temp ^ 0xA001;
}
}
/*** Reverse byte order. ***/
temp2=temp >> 8;
temp= (temp << 8) | temp2;
return(temp);
}
int check_master_crc (void) {
int diff, diff1, diff2; //what we send back char data[6]; result = 0x0000; crc_high = 0x00; crc_low = 0x00; data[0] = address; //this is ugly but all master queries are 6bytes+CRC so it'll do data[1] = ctxt[0]; //function data[2] = ctxt[1]; //start_addressH data[3] = ctxt[2]; //start_addressL data[4] = ctxt[3]; //# of regH data[5] = ctxt[4]; //# of regL result = modbus_crc(data,0,6); crc_high = result >> 8; crc_low = result & 0x00FF; diff1 = ctxt[5] ^ crc_high; diff2 = ctxt[6] ^ crc_low; diff = diff1 + diff2; //this spits back an XORed value between calculated and received CRC return(diff);
}
void respond_input_status(void) {
//ctxt[1] start_addressH
//ctxt[2] start_addressL
//ctxt[3] # of regH
//ctxt[4] # of regL
querylength = ctxt[4]; //ignoring upper byte for now (TODO)
query_reg_address = ctxt[2]; //ignoring upper address byte (TODO)
frame = 0; //data frame counter do { data_out[frame] = reg_address[query_reg_address]; //TODO make this do somethng querylength--; frame++; } while (querylength>0); querylength = ctxt[4]; //reload so we can count time transmitting data result = modbus_crc(data_out,0,2); crc_high = result >> 8; crc_low = result & 0x00FF; //uart_xmit response address, function, data_out[frames], CRC uart_xmit(address); uart_xmit(ctxt[0]); frame = 0; do { uart_xmit(data_out[frame]); //TODO make this do somethng querylength--; frame++; } while (querylength>0); uart_xmit(crc_high); uart_xmit(crc_low);
}
int main(void) {
OSCCONbits.SCS = 0x00;
new_rx=0; init_io(); serial_init(); run_timer(); check_my_address(); y += address; write_uart("MODBus "); uart_xmit(y); LATCbits.LATC0 = 1; LATCbits.LATC1 = 1; __delay_10ms(50); LATCbits.LATC0 = 0; LATCbits.LATC1 = 0; if (address == 0x01){ LATCbits.LATC0 = 1; } if (address == 0x02) { LATCbits.LATC1 = 1; } __delay_10ms(50); LATCbits.LATC0 = 0; LATCbits.LATC1 = 0; INTCONbits.GIE = 1; INTCONbits.PEIE = 1; while (1) { //If read data, not me and then if no 232 traffic for >800us then any xmit over //104 us (9600) x 8 quiet periods minus some for wiggle. if (ping) { run_timer(); } if (new_rx) { //this device was polled //testing response i = check_master_crc(); //check master query crc if (i==0x00) { //CRC Ok? if (ctxt[0] == 2) { //coil status query respond_input_status(); } //TODO other types of functions go here } //all done, get ready for new query new_rx = 0; } //testcrc[0] = 0xFF; //testcrc[1] = 0x02; //right now this is only thing processed //result = modbus_crc(testcrc,0,2); //crc_high = result >> 8; //crc_low = result & 0x00FF; __delay_10ms(10); LATCbits.LATC2 = 1; //LED blinky test __delay_10ms(10); LATCbits.LATC0 = 0; LATCbits.LATC1 = 0; LATCbits.LATC2 = 0; }
}
I just finished up a basic tutorial of using TIA-485 (RS-485/EIA-485) with a PIC micro on 0xEE.net.. this will be part one of at least 2 or 3 articles.