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;
}