RC2026 – Part 3 – Using a BME280 environmental sensor on an RC2014 with C

In my previous post, I showed how to read the ID register on the BME280 by bit-banging the SPI protocol using Z80 assembly language. I want to be able to read the temperature using the BME280, but this requires the use of floating-point maths to convert the values returned from the device to a value in degrees Celsius. I think this would be a bit too complex to do using Z80 assembly language, so I plan to switch to using C instead.

I have previously written about using the z88dk C compiler with an RC2014, so I won’t fully go over this again. I will rewrite the previous assembly language code to get the ID of the BME280 device and display it on the LEDs.

Pragmas

I need to set a couple of pragma’s in the C code to tell the compiler where to assemble the code and where to set the stack pointer. The assembly language program ran from address 0x9000, so I will do the same here. I will also set the stack pointer to 0xFC00.

#pragma output CRT_ORG_CODE = 0x9000
#pragma output REGISTER_SP = 0xFC00

Z80 IO Ports

To use the Z80’s IO ports from C, I need to use a special function register. The C compiler z88dk uses has a special instruction called __sfr __at that declares a variable at a specific IO port. This variable can then be read from or assigned, and this maps to Z80 in and out instructions. I’m going to be using IO port 0x01 again.

#define __IO_SPI_PORT 0x01
__sfr __at __IO_SPI_PORT io_spi;

Sending and receiving a byte of data using SPI

Sending and receiving a byte of data takes place in parallel, so I’m going to create a C function that takes a byte as a parameter and also returns a byte.

First, I need to include stdint.h to get the required data type of uint8_t. I also need to define the bit masks for MOSI, MISO, CLK, and SELECT.

#include <stdint.h>

#define MOSI 0b00000001
#define MISO 0b00000001
#define CLK  0b00000010
#define SELECT 0b00001000

Now for the actual transfer function.

// Send a byte and receive a byte from the BME280 over SPI. 
uint8_t transfer(uint8_t value) {
    uint8_t received_value = 0;
    for (int i = 0; i < 8; i++) {
        // Set the MOSI line based on the current bit of the value to send,
        // then det the CLK line low.
        if (value & 0x80) {
            io_spi = 0 &~SELECT &~CLK | MOSI; // Set MOSI high and CLK low.
        } else {
            io_spi = 0 &~SELECT &~CLK; // Set MOSI low and CLK low.
        }
        
        // Read the MISO line to get the received bit.
        uint8_t miso = io_spi & MISO; // Read MISO

        // Shift the received value left and add the new bit.
        received_value = (received_value << 1) | (miso ? 1 : 0);
        
        // Pulse the clock line high.
        if (value & 0x80) {
            io_spi = 0 &~SELECT | CLK | MOSI; // Set MOSI high and CLK high.
        } else {
            io_spi = 0 &~SELECT | CLK; // Set MOSI low and CLK high.
        }
        
        // Prepare for the next bit by shifting the value to send left
        value <<= 1;
    }
    return received_value;
}

Reading the ID byte and displaying the result

Now I have a function that can send and receive a byte; I can request the ID byte.

Firstly, I need to make sure the device isn’t selected. Next, I need to request the ID by sending the command 0xD0, then read the returned byte. I need to send a dummy value when reading the returned byte, so I will use 0x00 in this case. Finally, I deselect the BME280 and show the values on the LEDs, as with the assembly language version.

int main(void) {
    io_spi = SELECT | CLK | MOSI; // Deselect the BME280
    transfer(0xD0);               // send the address for the ID register.
    uint8_t id = transfer(0x00);  // Read the ID register.
    io_spi = SELECT | CLK | MOSI; // Deselect the BME280.

    io_spi = id;                  // display the ID value on the output port
    return 0;
}