The Pico In C: A 1-Wire PIO Program
Written by Harry Fairhead   
Wednesday, 28 April 2021
Article Index
The Pico In C: A 1-Wire PIO Program
A PIO Project
Reading The Temperature

The PIO program is fairly general in that you could use it to talk to almost any 1-Wire device, but we are interested in the DS18B20 and it is easy enough to implement a function to read the temperature along the lines of the previous function:

float getTemperature(PIO pio, uint sm)
{

First we send an initialization pulse followed by a Skip ROM and a Convert command. The first item sets a write operation with an initialization pulse:

    pio_sm_put_blocking(pio, sm, 250);
pio_sm_put_blocking(pio, sm, 1);
pio_sm_put_blocking(pio, sm, 0xCC);
pio_sm_put_blocking(pio, sm, 0x44);

Next we should wait until the conversion is complete, but for simplicity we wait for 1s – after which time the conversion is either complete or the device is broken. Then we send another initialization pulse, a Skip ROM and a Read ScratchPad command:

    sleep_ms(1000);
pio_sm_put_blocking(pio, sm, 250);
pio_sm_put_blocking(pio, sm, 1);
pio_sm_put_blocking(pio, sm, 0xCC);
pio_sm_put_blocking(pio, sm, 0xBE);

Finally we send a read command for nine bytes of data:

    pio_sm_put_blocking(pio, sm, 0);
pio_sm_put_blocking(pio, sm, 8);

and read the nine bytes from the FIFO:

    int i;
uint8_t data[9];
for (i = 0; i < 9; i++)
{
data[i] = pio_sm_get_blocking(pio, sm) >> 24;
}

Now we have the same nine bytes of data as in the bit-banging example and the rest of the program is identical.

A better idea is to create two functions which read and write an array of bytes to the device:

void writeBytes(PIO pio, uint sm,uint8_t bytes[],
int len){
pio_sm_put_blocking(pio, sm, 250);
pio_sm_put_blocking(pio, sm, len-1);
for(int i=0;i<len;i++){
pio_sm_put_blocking(pio, sm, bytes[i]);
}
}
void readBytes(PIO pio, uint sm,uint8_t bytes[],
int len){
pio_sm_put_blocking(pio, sm, 0);
pio_sm_put_blocking(pio, sm, len-1);
for(int i=0;i<len;i++){
bytes[i]=pio_sm_get_blocking(pio, sm) >> 24;
}
}

Using these two functions the interaction with the 1-Wire device starts to look a lot like using the SPI or I2C bus.

This is about as complex a PIO program as you can implement. This raises the question of what to do if you need something more complex? You might think that using additional state machines might help, but you are still limited to 32 instructions in total. There are two possible practical solutions. You can use immediately executed instructions put directly into the state machine from the C program. This works for small tasks such as initialization. The second solution is more generally useful. If you can split your program into small, less than 32 instructions, modules then you can load one into one PIO and another into the second PIO. This, of course, occupies both PIOs and you can’t use a PIO for another task. For example, you could use one PIO to send data to a 1-Wire bus device and the other to receive data. This approach is limited to just two modules, so a total of 64 instructions. You can extend this idea to any number of modules if they can be reloaded into the same PIO. For example, by splitting the 1‑Wire code into a receive and a send module you could easily afford the time to load each module and set it up into the same PIO.

The current program can only work with a single device on the 1-Wire bus. If you want to support more, you could implement the search algorithm outlined in Chapter 15 of Raspberry Pi IOT in C, Second Edition, though this is complex. A simpler solution is to use one state machine and one GPIO line per device. This can support up to eight different 1-Wire devices.

The Complete Program

The C program using the read and write functions is:

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "DS1820.pio.h"
uint8_t crc8(uint8_t *data, uint8_t len)
{
    uint8_t i;
    uint8_t j;
    uint8_t temp;
    uint8_t databyte;
    uint8_t crc = 0;
    for (i = 0; i < len; i++)
    {
        databyte = data[i];
        for (j = 0; j < 8; j++)
        {
            temp = (crc ^ databyte) & 0x01;
            crc >>= 1;
            if (temp)
                crc ^= 0x8C;
            databyte >>= 1;
        }
    }
    return crc;
}
void writeBytes(PIO pio, uint sm, uint8_t bytes[],
int len) { pio_sm_put_blocking(pio, sm, 250); pio_sm_put_blocking(pio, sm, len - 1); for (int i = 0; i < len; i++) { pio_sm_put_blocking(pio, sm, bytes[i]); } } void readBytes(PIO pio, uint sm, uint8_t bytes[],
int len) { pio_sm_put_blocking(pio, sm, 0); pio_sm_put_blocking(pio, sm, len - 1); for (int i = 0; i < len; i++) { bytes[i] = pio_sm_get_blocking(pio, sm) >> 24; } } float getTemperature(PIO pio, uint sm) { writeBytes(pio, sm, (uint8_t[]){0xCC, 0x44}, 2); sleep_ms(1000); writeBytes(pio, sm, (uint8_t[]){0xCC, 0xBE}, 2); uint8_t data[9]; readBytes(pio, sm, data, 9); uint8_t crc = crc8(data, 9); if (crc != 0) return -2000; int t1 = data[0]; int t2 = data[1]; int16_t temp1 = (t2 << 8 | t1); volatile float temp = (float)temp1 / 16; return temp; } uint DS18Initalize(PIO pio, int gpio) { uint offset = pio_add_program(pio, &DS1820_program); uint sm = pio_claim_unused_sm(pio, true); pio_gpio_init(pio, gpio); pio_sm_config c = DS1820_program_get_default_config(
offset); sm_config_set_clkdiv_int_frac(&c, 255, 0); sm_config_set_set_pins(&c, gpio, 1); sm_config_set_out_pins(&c, gpio, 1); sm_config_set_in_pins(&c, gpio); sm_config_set_in_shift(&c, true, true, 8); pio_sm_init(pio0, sm, offset, &c); pio_sm_set_enabled(pio0, sm, true); return sm; }
int main()
{
    stdio_init_all();
    uint sm = DS18Initalize(pio0, 2);
    float t;
    for (;;)
    {
        do
        {
            t = getTemperature(pio0, sm);
        } while (t < -999);
        printf("temperature %f\r\n", t);
        sleep_ms(500);
    };
    return 0;
}

The PIO program is:

.program DS1820 
.wrap_target
again: pull block mov x, osr jmp !x, read write: set pindirs, 1 set pins, 0 loop1: jmp x--,loop1
set pindirs, 0 [31] wait 1 pin 0 [31] pull block mov x, osr bytes1: pull block set y, 7 set pindirs, 1 bit1: set pins, 0 [1] out pins,1 [31] set pins, 1 [20] jmp y--,bit1 jmp x--,bytes1 set pindirs, 0 [31] jmp again read: pull block mov x, osr bytes2: set y, 7 bit2: set pindirs, 1 set pins, 0 [1] set pindirs, 0 [5] in pins,1 [10] jmp y--,bit2 jmp x--,bytes2 .wrap

The CMakeLists file is:

cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) include(pico_sdk_import.cmake)
project(ds18 C CXX ASM) pico_sdk_init() add_executable(ds18 DS1820.c )
pico_generate_pio_header(ds18
${CMAKE_CURRENT_LIST_DIR}/DS1820.pio)
target_link_libraries(ds18 pico_stdlib
hardware_gpio hardware_pio)
pico_add_extra_outputs(ds18)

 

Summary

  • The 1-Wire bus is a proprietary, but widely-used, bus. It is simple and very capable.

  • As its name suggests it makes use of a single data wire and usually a power supply and ground.

  • It is possible to dispense with the power line and the connected device will draw power from the data line.

  • Implementing the 1-Wire protocol is mostly a matter of getting the timing right.

  • There are three types of interaction: presence pulse, read and write.

  • The presence pulse simply asks any connected devices to reply and make themselves known.

  • The 1-Wire protocol is easier to implement than you might think because each bit is sent as a “slot” and while timing is critical within the slot, how fast slots are sent isn’t and the master is in control of when this happens.

  • The DS18B20 temperature sensor is one of the most commonly encountered 1‑Wire bus devices. It is small, low-cost and you can use multiple devices on a single bus.

  • After a convert command is sent to the device, it can take 750ms before a reading is ready.

  • To test for data ready you have to poll on a single bit. Reading a zero means data not ready and reading a one means data ready.

  • When the data is ready you can read the scratchpad memory where the data is stored.

  • The DS18B20 has other commands that can be used to set temperature alarms etc, but these are rarely used.

  • With a shift in viewpoint it is just possible to squeeze a 1-Wire bus protocol into a 32 instruction PIO program.

<ASIN:B08W3SH4TD>

<ASIN:B08TRV2PJY>




Programming the Raspberry Pi Pico In C

By Harry Fairhead

picoC2E360

Buy from Amazon.

Contents

  • Preface
  • Chapter 1 The Raspberry Pi Pico – Before We Begin
  • Chapter 2 Getting Started
  • Chapter 3 Getting Started With The GPIO
  • Chapter 4 Simple Output
  • Chapter 5 Some Electronics
  • Chapter 6 Simple Input
        Extract:   GPIO Input ***NEW!
  • Chapter 7 Advanced Input – Events and Interrupts
  • Chapter 8 Pulse Width Modulation
        Extract: Basic PWM
  • Chapter 9 Controlling Motors And Servos
  • Chapter 10 Getting Started With The SPI Bus
  • Chapter 11 A-To-D and The SPI Bus
  • Chapter 12 Using The I2C Bus
  • Chapter 13 Using The PIO
        Extract: A 1-Wire PIO Program  
  • Chapter 14 The DHT22 Sensor Implementing A Custom Protocol
  • Chapter 15 The 1‑Wire Bus And The DS1820
  • Chapter 16 The Serial Port
  • Chapter 17 Using the Pico W
       Extract: Simple Web Client
       Extract:A Better Connect
  • Chapter 18 The Pico/W In C: Direct To Hardware 

Extra: Adding WiFi To The Pico 2

<ASIN:1871962803>

<ASIN:187196279X>

 

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.

Banner


Apache Releases Tomcat 11
07/11/2024

Apache has announced the release of Tomcat 11, as well as marking the 25th anniversary of the first commit to the Apache Tomcat source code repository since becoming an ASF project.



Ursina - A Game Engine Powered by Python
08/11/2024

Ursina is a new open source game engine in which you can code any type of game in Python, be it 2-D, 3-D, an application, a visualization, you name it.


More News

espbook

 

Comments




or email your comment to: comments@i-programmer.info



Last Updated ( Wednesday, 02 November 2022 )