diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b61206f..65c69138 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ cmake_minimum_required(VERSION 3.10) set(CMAKE_CXX_STANDARD 17) # Select the test project to build -set(selected_test_project hello_world) +set(selected_test_project access_point) # For Linux builds, you may enable address sanitizer set(SMOOTH_ENABLE_ASAN 0) @@ -50,6 +50,8 @@ list(APPEND available_tests linux_asan_test linux_unit_tests hw_wrover_kit_blinky + i2c_bme280_test + spi_4_line_devices_test ) list(FIND available_tests ${selected_test_project} test_found) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 740f61b6..a84091cb 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -9,3 +9,4 @@ Below is the list of individuals that have contributed to the project. |[KerryRJ](https://github.com/KerryRJ)|[PR#71](https://github.com/PerMalmberg/Smooth/pull/71)| |[jeremyjh](https://github.com/jeremyjh)|[PR#87](https://github.com/PerMalmberg/Smooth/pull/87)| |[COM8](https://github.com/COM8)|[PR#98](https://github.com/PerMalmberg/Smooth/pull/98)| +|[enelson1001](https://github.com/enelson1001)| diff --git a/Documents/ILI9341.pdf b/Documents/ILI9341.pdf new file mode 100644 index 00000000..ef9388c8 Binary files /dev/null and b/Documents/ILI9341.pdf differ diff --git a/lib/files.cmake b/lib/files.cmake index 0e2a4f4b..64beac50 100644 --- a/lib/files.cmake +++ b/lib/files.cmake @@ -4,12 +4,15 @@ set(smooth_dir ${CMAKE_CURRENT_LIST_DIR}/smooth) set(smooth_inc_dir ${CMAKE_CURRENT_LIST_DIR}/smooth/include/smooth) set(SMOOTH_SOURCES + ${smooth_dir}/application/display/ILI9341.cpp ${smooth_dir}/application/hash/base64.cpp ${smooth_dir}/application/hash/sha.cpp ${smooth_dir}/application/io/i2c/ADS1115.cpp ${smooth_dir}/application/io/i2c/BME280.cpp ${smooth_dir}/application/io/i2c/MCP23017.cpp ${smooth_dir}/application/io/i2c/MCP23017.cpp + ${smooth_dir}/application/io/spi/BME280SPI.cpp + ${smooth_dir}/application/io/spi/BME280Core.cpp ${smooth_dir}/application/io/wiegand/Wiegand.cpp ${smooth_dir}/application/network/http/HTTPProtocol.cpp ${smooth_dir}/application/network/http/HTTPServerClient.cpp @@ -68,6 +71,8 @@ set(SMOOTH_SOURCES ${smooth_dir}/core/io/i2c/I2CCommandLink.cpp ${smooth_dir}/core/io/i2c/I2CMasterDevice.cpp ${smooth_dir}/core/io/i2c/Master.cpp + ${smooth_dir}/core/io/spi/Master.cpp + ${smooth_dir}/core/io/spi/SPIDevice.cpp ${smooth_dir}/core/io/Input.cpp ${smooth_dir}/core/io/InterruptInput.cpp ${smooth_dir}/core/io/InterruptInputCB.cpp @@ -88,6 +93,10 @@ set(SMOOTH_SOURCES ${smooth_dir}/core/timer/Timer.cpp ${smooth_dir}/core/timer/TimerService.cpp ${smooth_dir}/core/util/string_util.cpp + ${smooth_inc_dir}/application/display/ILI9341.h + ${smooth_inc_dir}/application/display/ILI9341_init_cmds.h + ${smooth_inc_dir}/application/io/spi/BME280SPI.h + ${smooth_inc_dir}/application/io/spi/BME280Core.h ${smooth_inc_dir}/application/io/i2c/ADS1115.h ${smooth_inc_dir}/application/io/i2c/MCP23017.h ${smooth_inc_dir}/application/network/http/HTTPProtocol.h @@ -154,6 +163,9 @@ set(SMOOTH_SOURCES ${smooth_inc_dir}/core/io/i2c/I2CCommandLink.h ${smooth_inc_dir}/core/io/i2c/I2CMasterDevice.h ${smooth_inc_dir}/core/io/i2c/Master.h + ${smooth_inc_dir}/core/io/spi/Master.h + ${smooth_inc_dir}/core/io/spi/SPIDevice.h + ${smooth_inc_dir}/core/io/spi/SpiDmaFixedBuffer.h ${smooth_inc_dir}/core/io/Input.h ${smooth_inc_dir}/core/io/Input.h ${smooth_inc_dir}/core/io/InterruptInput.h diff --git a/lib/smooth/application/display/ILI9341.cpp b/lib/smooth/application/display/ILI9341.cpp new file mode 100644 index 00000000..e111ab13 --- /dev/null +++ b/lib/smooth/application/display/ILI9341.cpp @@ -0,0 +1,396 @@ +/* +Smooth - A C++ framework for embedded programming on top of Espressif's ESP-IDF +Copyright 2019 Per Malmberg (https://gitbub.com/PerMalmberg) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#include +#include +#include "smooth/application/display/ILI9341.h" +#include "smooth/application/display/ILI9341_init_cmds.h" +#include "smooth/core/io/spi/SpiDmaFixedBuffer.h" +#include "smooth/core/logging/log.h" + +using namespace smooth::core::logging; +using namespace smooth::core::io::spi; + +namespace smooth::application::display +{ + static const char* TAG = "ILI9314"; + static const bool PIN_HIGH = true; + static const bool PIN_LOW = false; + + ILI9341::ILI9341(std::mutex& guard, + gpio_num_t chip_select_pin, + gpio_num_t data_command_pin, + gpio_num_t reset_pin, + gpio_num_t back_light_pin, + uint8_t spi_command_bits, + uint8_t spi_address_bits, + uint8_t bits_between_address_and_data_phase, + uint8_t spi_mode, + uint8_t spi_positive_duty_cycle, + uint8_t spi_cs_ena_posttrans, + int spi_clock_speed_hz, + uint32_t spi_device_flags, + int spi_queue_size, + bool use_pre_transaction_callback, + bool use_post_transaction_callback) + : SPIDevice(guard, + spi_command_bits, + spi_address_bits, + bits_between_address_and_data_phase, + spi_mode, + spi_positive_duty_cycle, + spi_cs_ena_posttrans, + spi_clock_speed_hz, + spi_device_flags, + spi_queue_size, + use_pre_transaction_callback, + use_post_transaction_callback), + + reset_pin(reset_pin, true, false, false), + backlight_pin(back_light_pin, true, false, false), + dc_pin(data_command_pin, true, false, false), + cs_pin(chip_select_pin, true, false, false) + { + } + + // Initialize the display + bool ILI9341::init(spi_host_device_t host) + { + // spi_transaction will not control chip select + bool res = initialize(host, GPIO_NUM_NC); + + return res; + } + + // I have found different displays set the value of madctl differently + // for the same rotation. For example M5Stack sets madctl to 0x68 for + // portrait while the adafruit 2.88 dipslay sets madctl to 0x48. To make + // driver more versatile I have used madctl_value instead of the typical + // 0-3 screen_rotation_value that is used in switch statement. + // If you are using LittlevGL then you need to set the appropriate screen + // width and screen height for the rotation you have chosen in the lvconf.h file. + bool ILI9341::set_rotation(uint8_t madctl_value) + { + bool res = lcd_wr_cmd(0x36); + res &= lcd_wr_data(&madctl_value, 1); + + return res; + } + + // Reset the display + void ILI9341::reset_display() + { + reset_pin.set(PIN_LOW); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + reset_pin.set(PIN_HIGH); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + // Send initialize sequence of commands and data to display + bool ILI9341::send_init_cmds() + { + bool res = true; + + uint16_t seq_num = 0; + + while (ili_init_cmds.at(seq_num).databytes != 0xff) + { + // check if delay is needed; send only command then delay + if (ili_init_cmds.at(seq_num).databytes & 0x80) + { + res &= lcd_wr_cmd(ili_init_cmds.at(seq_num).cmd); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + else + { + res &= lcd_wr_cmd(ili_init_cmds.at(seq_num).cmd); + res &= lcd_wr_data(ili_init_cmds.at(seq_num).data, ili_init_cmds.at(seq_num).databytes & 0x1F); + } + + // advance to next sequence in ili_ints_cmds + seq_num++; + } + + return res; + } + + // Send a command to the LCD. Uses spi_device_polling_write, which waits + // until the transfer is complete. + // + // Since command transactions are usually small, they are handled in polling + // mode for higher speed. The overhead of interrupt transactions is more than + // just waiting for the transaction to complete. + bool ILI9341::lcd_wr_cmd(const uint8_t cmd) + { + // reset current transaction counter + current_transaction = 0; + + // setup pre and post control pin states + dc_pretrans_pin_states.at(0) = PIN_LOW; + cs_pretrans_pin_states.at(0) = PIN_LOW; + cs_posttrans_pin_states.at(0) = PIN_HIGH; + + std::lock_guard lock(get_guard()); + spi_transaction_t trans; + std::memset(&trans, 0, sizeof(trans)); //Zero out the transaction + trans.rx_buffer = NULL; + trans.rxlength = 0; + trans.length = 8; + trans.tx_buffer = &cmd; + + bool res = polling_write(trans); + + return res; + } + + // Send data to the LCD. Uses spi_device_polling_write, which waits until the + // transfer is complete. + bool ILI9341::lcd_wr_data(const uint8_t* data, size_t length) + { + // reset current transaction counter + current_transaction = 0; + + // setup pre and post control pin states + dc_pretrans_pin_states.at(0) = PIN_HIGH; + cs_pretrans_pin_states.at(0) = PIN_LOW; + cs_posttrans_pin_states.at(0) = PIN_HIGH; + + std::lock_guard lock(get_guard()); + spi_transaction_t trans; + std::memset(&trans, 0, sizeof(trans)); //Zero out the transaction + trans.rx_buffer = NULL; + trans.rxlength = 0; + trans.length = length * 8; + trans.tx_buffer = data; + + bool res = polling_write(trans); + + return res; + } + + // To send a set of lines we have to send a command, 2 data bytes, another command, + // 2 more data bytes and another command before sending the lines of data itself; + // a total of 6 transactions. (We can't put all of this in just one transaction + // because the D/C line needs to be toggled in the middle.) + // This routine queues these commands up as interrupt transactions so they get + // sent faster (compared to calling spi_device_transmit several times), and + // meanwhile the lines for next transactions can get calculated. + bool ILI9341::send_lines(int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint8_t* data, size_t length) + { + // if length is 0 nothing to do and probably an error so return + bool res = length > 0; + + if (res) + { + std::lock_guard lock(get_guard()); + + // reset current transaction counter + current_transaction = 0; + + // Transaction descriptors. Declared static so they're not allocated on the + // stack; we need this memory even when this function is finished because + // the SPI driver needs access to it even while we're already calculating + // the next line. + static std::array trans; + + // In theory, it's better to initialize trans and data only once and hang + // on to the initialized variables. We allocate them on the stack, so we + // don't need to re-init them each call. + for (uint8_t x = 0; x < 6; x++) + { + //Zero out the transaction + std::memset(&trans.at(x), 0, sizeof(spi_transaction_t)); + + if ((x & 1) == 0) + { + // Even transfers are commands + trans.at(x).rx_buffer = NULL; + trans.at(x).rxlength = 0; + trans.at(x).length = 8; + + // setup pre and post states + dc_pretrans_pin_states.at(x) = PIN_LOW; + cs_pretrans_pin_states.at(x) = PIN_LOW; + cs_posttrans_pin_states.at(x) = PIN_HIGH; + } + else + { + // Odd transfers are data + trans.at(x).rx_buffer = NULL; + trans.at(x).rxlength = 0; + trans.at(x).length = 8 * 4; + + // setup pre and post states + dc_pretrans_pin_states.at(x) = PIN_HIGH; + cs_pretrans_pin_states.at(x) = PIN_LOW; + cs_posttrans_pin_states.at(x) = PIN_HIGH; + } + + trans.at(x).flags = SPI_TRANS_USE_TXDATA; + } + + // Column addresses + trans.at(0).tx_data[0] = 0x2A; + trans.at(1).tx_data[0] = static_cast((x1 >> 8) & 0xFF); // Start Col High + trans.at(1).tx_data[1] = static_cast(x1 & 0xFF); // Start Col Low + trans.at(1).tx_data[2] = static_cast((x2 >> 8) & 0xFF); // End Col High + trans.at(1).tx_data[3] = static_cast(x2 & 0xFF); // End Col Low + + // Page addresses + trans.at(2).tx_data[0] = 0x2B; + trans.at(3).tx_data[0] = static_cast((y1 >> 8) & 0xFF); // Start page high + trans.at(3).tx_data[1] = static_cast(y1 & 0xFF); // start page low + trans.at(3).tx_data[2] = static_cast((y2 >> 8) & 0xFF); // end page high + trans.at(3).tx_data[3] = static_cast(y2 & 0xFF); // end page low + + // Ram Write + trans.at(4).tx_data[0] = 0x2C; // memory write + + // Data to send + trans.at(5).tx_buffer = data; + trans.at(5).length = length * 8; + trans.at(5).flags = 0; // clear flags + + // Queue all transactions. + for (uint8_t i = 0; i < 6; i++) + { + res &= queue_transaction(trans.at(i)); + } + } + + return res; + } + + // Wait for queued transactions to finish + bool ILI9341::wait_for_send_lines_to_finish() + { + bool res = true; + + std::lock_guard lock(get_guard()); + + for (uint8_t x = 0; x < 6; x++) + { + res &= wait_for_transaction_to_finish(); + } + + return res; + } + + // There are a few registers that can be read from the ILI9341 and some + // EXTENDED Command set of registers that some displays may support (page 85) + // + // See pages 36-39 of the ili9341 data sheet that relates to the following discussion. + // When reading more than 1 parameter, the ILI9341 requires one dummy clock cycle before + // reading the data. For example Reg 0x04 Read Display ID info requires 25 clock cycles. + // It is not easy to do this with the ESP32 SPI, so what we will do is add an extra byte + // to read and produce 32 clock cycles. The data read will have to be manipulated because + // the MSB bit of the first parameter read contains the dummy clock data bit. We need to + // shift out the dummy data bit and recovery the MSB bit from the extra byte added to the read. + // NOTE: FOR READING PARAMS SCK must be 16MHz or less the datasheet recommends 10MHz. + bool ILI9341::read_params(uint8_t cmd, std::vector& data, uint32_t param_count) + { + SpiDmaFixedBuffer rxdata; + + bool res = false; + + if (param_count == 1) + { + res = read(cmd, rxdata.data(), 1); + data.push_back(rxdata[0]); + } + else if (param_count > 1 && param_count < 16) + { + // increment param_count by 1 so an additional byte is read and we get all the data + res = read(cmd, rxdata.data(), param_count + 1); + + // Since we are using 4-line spi (full duplex) the sck clocks both data out + // and data in at the same time. The first byte going out is the register we want + // to read and so the first byte received is garbage or dummy byte. So we + // don't want to copy the dummy byte but copy only the real data + for (size_t i = 0; i < param_count; i++) + { + data.push_back( static_cast((rxdata[i] << 1) | (rxdata[i + 1] >> 7))); + } + } + else + { + Log::error(TAG, "read_params() param_count must be greater than 0 or less than 16"); + } + + return res; + } + + // Read a register or a sequence of registers from the display + bool ILI9341::read(uint8_t cmd, uint8_t* rxdata, size_t length) + { + // if length is 0 nothing to do and probably an error so return + bool res = length > 0; + + if (res) + { + std::lock_guard lock(get_guard()); + + // create 2 spi transactions; one for command and one for reading parameters + std::array trans; + + for (uint8_t x = 0; x < 2; x++) + { + //Zero out the transaction + std::memset(&trans.at(x), 0, sizeof(spi_transaction_t)); + } + + // reset current transaction counter + current_transaction = 0; + + // configure command transaction + trans.at(0).rx_buffer = NULL; + trans.at(0).rxlength = 0; + trans.at(0).length = 8; + trans.at(0).tx_data[0] = cmd; + trans.at(0).flags = SPI_TRANS_USE_TXDATA; + + // setup pre and post control pin states + dc_pretrans_pin_states.at(0) = PIN_LOW; + cs_pretrans_pin_states.at(0) = PIN_LOW; + cs_posttrans_pin_states.at(0) = PIN_LOW; // keep chip select low + + // configure read parameters transaction + trans.at(1).rx_buffer = rxdata; + trans.at(1).rxlength = 8 * length; + trans.at(1).length = 8 * length; + trans.at(1).tx_buffer = NULL; + + // setup pre and post control pin states + dc_pretrans_pin_states.at(1) = PIN_HIGH; + cs_pretrans_pin_states.at(1) = PIN_LOW; + cs_posttrans_pin_states.at(1) = PIN_HIGH; + + // Queue the 2 transactions to be sent + for (uint8_t i = 0; i < 2; i++) + { + res &= queue_transaction(trans.at(i)); + } + + // wait for the 2 transaction to finish + for (uint8_t x = 0; x < 2; x++) + { + res &= wait_for_transaction_to_finish(); + } + } + + return res; + } +} diff --git a/lib/smooth/application/files.cmake b/lib/smooth/application/files.cmake new file mode 100644 index 00000000..55952436 --- /dev/null +++ b/lib/smooth/application/files.cmake @@ -0,0 +1,179 @@ +set(SMOOTH_LIB_ROOT ${CMAKE_CURRENT_LIST_DIR}) + +set(smooth_dir ${CMAKE_CURRENT_LIST_DIR}/smooth) +set(smooth_inc_dir ${CMAKE_CURRENT_LIST_DIR}/smooth/include/smooth) + +set(SMOOTH_SOURCES + ${smooth_dir}/application/display/ILI9341.cpp + ${smooth_dir}/application/hash/base64.cpp + ${smooth_dir}/application/hash/sha.cpp + ${smooth_dir}/application/io/i2c/ADS1115.cpp + ${smooth_dir}/application/io/i2c/BME280.cpp + ${smooth_dir}/application/io/i2c/MCP23017.cpp + ${smooth_dir}/application/io/i2c/MCP23017.cpp + ${smooth_dir}/application/io/spi/BME280SPI.cpp + ${smooth_dir}/application/io/spi/BME280Core.cpp + ${smooth_dir}/application/io/wiegand/Wiegand.cpp + ${smooth_dir}/application/network/http/HTTPProtocol.cpp + ${smooth_dir}/application/network/http/HTTPServerClient.cpp + ${smooth_dir}/application/network/http/http_utils.cpp + ${smooth_dir}/application/network/http/regular/HTTPHeaderDef.cpp + ${smooth_dir}/application/network/http/regular/HTTPPacket.cpp + ${smooth_dir}/application/network/http/regular/MIMEParser.cpp + ${smooth_dir}/application/network/http/regular/RegularHTTPProtocol.cpp + ${smooth_dir}/application/network/http/regular/responses/ErrorResponse.cpp + ${smooth_dir}/application/network/http/regular/responses/FileContentResponse.cpp + ${smooth_dir}/application/network/http/regular/responses/HeaderOnlyResponse.cpp + ${smooth_dir}/application/network/http/regular/responses/StringResponse.cpp + ${smooth_dir}/application/network/http/regular/TemplateProcessor.cpp + ${smooth_dir}/application/network/http/URLEncoding.cpp + ${smooth_dir}/application/network/http/websocket/responses/WSResponse.cpp + ${smooth_dir}/application/network/http/websocket/WebsocketProtocol.cpp + ${smooth_dir}/application/network/http/websocket/WebSocketServer.cpp + ${smooth_dir}/application/network/mqtt/MqttClient.cpp + ${smooth_dir}/application/network/mqtt/packet/ConnAck.cpp + ${smooth_dir}/application/network/mqtt/packet/Connect.cpp + ${smooth_dir}/application/network/mqtt/packet/Disconnect.cpp + ${smooth_dir}/application/network/mqtt/packet/MQTTPacket.cpp + ${smooth_dir}/application/network/mqtt/packet/MQTTProtocol.cpp + ${smooth_dir}/application/network/mqtt/packet/MQTTProtocolDefinitions.cpp + ${smooth_dir}/application/network/mqtt/packet/PacketDecoder.cpp + ${smooth_dir}/application/network/mqtt/packet/PacketIdentifierFactory.cpp + ${smooth_dir}/application/network/mqtt/packet/PingReq.cpp + ${smooth_dir}/application/network/mqtt/packet/PingResp.cpp + ${smooth_dir}/application/network/mqtt/packet/PubAck.cpp + ${smooth_dir}/application/network/mqtt/packet/PubComp.cpp + ${smooth_dir}/application/network/mqtt/packet/Publish.cpp + ${smooth_dir}/application/network/mqtt/packet/PubRec.cpp + ${smooth_dir}/application/network/mqtt/packet/PubRel.cpp + ${smooth_dir}/application/network/mqtt/packet/SubAck.cpp + ${smooth_dir}/application/network/mqtt/packet/Subscribe.cpp + ${smooth_dir}/application/network/mqtt/packet/UnsubAck.cpp + ${smooth_dir}/application/network/mqtt/packet/Unsubscribe.cpp + ${smooth_dir}/application/network/mqtt/Publication.cpp + ${smooth_dir}/application/network/mqtt/state/ConnectedState.cpp + ${smooth_dir}/application/network/mqtt/state/ConnectToBrokerState.cpp + ${smooth_dir}/application/network/mqtt/state/DisconnectedState.cpp + ${smooth_dir}/application/network/mqtt/state/DisconnectState.cpp + ${smooth_dir}/application/network/mqtt/state/MQTTBaseState.cpp + ${smooth_dir}/application/network/mqtt/state/RunState.cpp + ${smooth_dir}/application/network/mqtt/Subscription.cpp + ${smooth_dir}/application/security/PasswordHash.cpp + ${smooth_dir}/core/Application.cpp + ${smooth_dir}/core/filesystem/File.cpp + ${smooth_dir}/core/filesystem/filesystem.cpp + ${smooth_dir}/core/filesystem/FSLock.cpp + ${smooth_dir}/core/filesystem/MMCSDCard.cpp + ${smooth_dir}/core/filesystem/Path.cpp + ${smooth_dir}/core/filesystem/SDCard.cpp + ${smooth_dir}/core/filesystem/SPIFlash.cpp + ${smooth_dir}/core/filesystem/SPISDCard.cpp + ${smooth_dir}/core/io/i2c/I2CCommandLink.cpp + ${smooth_dir}/core/io/i2c/I2CMasterDevice.cpp + ${smooth_dir}/core/io/i2c/Master.cpp + ${smooth_dir}/core/io/spi/Master.cpp + ${smooth_dir}/core/io/spi/SPIDevice.cpp + ${smooth_dir}/core/io/Input.cpp + ${smooth_dir}/core/io/InterruptInput.cpp + ${smooth_dir}/core/io/InterruptInputCB.cpp + ${smooth_dir}/core/io/Output.cpp + ${smooth_dir}/core/ipc/QueueNotification.cpp + ${smooth_dir}/core/json/JsonFile.cpp + ${smooth_dir}/core/logging/log.cpp + ${smooth_dir}/core/network/CommonSocket.cpp + ${smooth_dir}/core/network/IPv4.cpp + ${smooth_dir}/core/network/IPv6.cpp + ${smooth_dir}/core/network/MbedTLSContext.cpp + ${smooth_dir}/core/network/SocketDispatcher.cpp + ${smooth_dir}/core/network/Wifi.cpp + ${smooth_dir}/core/sntp/Sntp.cpp + ${smooth_dir}/core/SystemStatistics.cpp + ${smooth_dir}/core/Task.cpp + ${smooth_dir}/core/timer/ElapsedTime.cpp + ${smooth_dir}/core/timer/Timer.cpp + ${smooth_dir}/core/timer/TimerService.cpp + ${smooth_dir}/core/util/string_util.cpp + ${smooth_inc_dir}/application/display/ILI9341.h + ${smooth_inc_dir}/application/display/ILI9341_init_cmds.h + ${smooth_inc_dir}/application/io/spi/BME280SPI.h + ${smooth_inc_dir}/application/io/spi/BME280Core.h + ${smooth_inc_dir}/application/io/i2c/ADS1115.h + ${smooth_inc_dir}/application/io/i2c/MCP23017.h + ${smooth_inc_dir}/application/network/http/HTTPProtocol.h + ${smooth_inc_dir}/application/network/http/HTTPServer.h + ${smooth_inc_dir}/application/network/http/HTTPServerClient.h + ${smooth_inc_dir}/application/network/http/HTTPServerConfig.h + ${smooth_inc_dir}/application/network/http/http_utils.h + ${smooth_inc_dir}/application/network/http/IResponseOperation.h + ${smooth_inc_dir}/application/network/http/regular/ITemplateDataRetriever.h + ${smooth_inc_dir}/application/network/http/regular/RegularHTTPProtocol.h + ${smooth_inc_dir}/application/network/http/regular/responses/ErrorResponse.h + ${smooth_inc_dir}/application/network/http/regular/responses/FileContentResponse.h + ${smooth_inc_dir}/application/network/http/regular/responses/StringResponse.h + ${smooth_inc_dir}/application/network/http/regular/TemplateProcessor.h + ${smooth_inc_dir}/application/network/http/URLEncoding.h + ${smooth_inc_dir}/application/network/http/websocket/WebsocketProtocol.h + ${smooth_inc_dir}/application/network/http/websocket/WebsocketServer.h + ${smooth_inc_dir}/application/network/mqtt/event/BaseEvent.h + ${smooth_inc_dir}/application/network/mqtt/event/ConnectEvent.h + ${smooth_inc_dir}/application/network/mqtt/event/DisconnectEvent.h + ${smooth_inc_dir}/application/network/mqtt/IMqttClient.h + ${smooth_inc_dir}/application/network/mqtt/InFlight.h + ${smooth_inc_dir}/application/network/mqtt/Logging.h + ${smooth_inc_dir}/application/network/mqtt/MqttClient.h + ${smooth_inc_dir}/application/network/mqtt/MQTTProtocolDefinitions.h + ${smooth_inc_dir}/application/network/mqtt/packet/ConnAck.h + ${smooth_inc_dir}/application/network/mqtt/packet/Connect.h + ${smooth_inc_dir}/application/network/mqtt/packet/Disconnect.h + ${smooth_inc_dir}/application/network/mqtt/packet/IPacketReceiver.h + ${smooth_inc_dir}/application/network/mqtt/packet/MQTTPacket.h + ${smooth_inc_dir}/application/network/mqtt/packet/MQTTProtocol.h + ${smooth_inc_dir}/application/network/mqtt/packet/PacketDecoder.h + ${smooth_inc_dir}/application/network/mqtt/packet/PacketIdentifierFactory.h + ${smooth_inc_dir}/application/network/mqtt/packet/PingReq.h + ${smooth_inc_dir}/application/network/mqtt/packet/PingResp.h + ${smooth_inc_dir}/application/network/mqtt/packet/PubAck.h + ${smooth_inc_dir}/application/network/mqtt/packet/PubComp.h + ${smooth_inc_dir}/application/network/mqtt/packet/Publish.h + ${smooth_inc_dir}/application/network/mqtt/packet/PubRec.h + ${smooth_inc_dir}/application/network/mqtt/packet/PubRel.h + ${smooth_inc_dir}/application/network/mqtt/packet/SubAck.h + ${smooth_inc_dir}/application/network/mqtt/packet/Subscribe.h + ${smooth_inc_dir}/application/network/mqtt/packet/UnsubAck.h + ${smooth_inc_dir}/application/network/mqtt/packet/Unsubscribe.h + ${smooth_inc_dir}/application/network/mqtt/Publication.h + ${smooth_inc_dir}/application/network/mqtt/state/ConnectedState.h + ${smooth_inc_dir}/application/network/mqtt/state/ConnectToBrokerState.h + ${smooth_inc_dir}/application/network/mqtt/state/DisconnectedState.h + ${smooth_inc_dir}/application/network/mqtt/state/DisconnectState.h + ${smooth_inc_dir}/application/network/mqtt/state/IdleState.h + ${smooth_inc_dir}/application/network/mqtt/state/MQTTBaseState.h + ${smooth_inc_dir}/application/network/mqtt/state/MqttFsmConstants.h + ${smooth_inc_dir}/application/network/mqtt/state/MqttFSM.h + ${smooth_inc_dir}/application/network/mqtt/state/RunState.h + ${smooth_inc_dir}/application/network/mqtt/state/StartupState.h + ${smooth_inc_dir}/application/network/mqtt/Subscription.h + ${smooth_inc_dir}/application/security/PasswordHash.h + ${smooth_inc_dir}/core/filesystem/MMCSDCard.h + ${smooth_inc_dir}/core/filesystem/MountPoint.h + ${smooth_inc_dir}/core/filesystem/Path.h + ${smooth_inc_dir}/core/filesystem/SDCard.h + ${smooth_inc_dir}/core/filesystem/SPIFlash.h + ${smooth_inc_dir}/core/filesystem/SPISDCard.h + ${smooth_inc_dir}/core/io/i2c/I2CCommandLink.h + ${smooth_inc_dir}/core/io/i2c/I2CMasterDevice.h + ${smooth_inc_dir}/core/io/i2c/Master.h + ${smooth_inc_dir}/core/io/spi/Master.h + ${smooth_inc_dir}/core/io/spi/SPIDevice.h + ${smooth_inc_dir}/core/io/Input.h + ${smooth_inc_dir}/core/io/Input.h + ${smooth_inc_dir}/core/io/InterruptInput.h + ${smooth_inc_dir}/core/io/InterruptInputCB.h + ${smooth_inc_dir}/core/io/Output.h + ${smooth_inc_dir}/core/network/Wifi.h + ${smooth_inc_dir}/core/sntp/Sntp.h + ${smooth_inc_dir}/core/sntp/TimeSyncEvent.h + ${smooth_inc_dir}/core/SystemStatistics.h + ) + + diff --git a/lib/smooth/application/io/i2c/BME280.cpp b/lib/smooth/application/io/i2c/BME280.cpp index 0733656a..b348ad51 100644 --- a/lib/smooth/application/io/i2c/BME280.cpp +++ b/lib/smooth/application/io/i2c/BME280.cpp @@ -14,15 +14,17 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include -#include #include "smooth/application/io/i2c/BME280.h" -#include "smooth/core/util/FixedBuffer.h" -#include "smooth/core/util/ByteSet.h" +#include "smooth/core/logging/log.h" + +using namespace smooth::core; +using namespace smooth::core::logging; namespace smooth::application::sensor { + static const char* TAG = "BME280SPI"; + BME280::BME280(i2c_port_t port, uint8_t address, std::mutex& guard) : I2CMasterDevice(port, address, guard) { @@ -31,7 +33,7 @@ namespace smooth::application::sensor uint8_t BME280::read_id() { core::util::FixedBuffer data; - auto res = read(address, ID_REG, data); + auto res = read(address, BME280Core::ID_REG, data); return res ? data[0] : 0; } @@ -39,7 +41,7 @@ namespace smooth::application::sensor bool BME280::reset() { // Write the magic number to the reset register - std::vector data{ RESET_REG, 0xB6 }; + std::vector data{ BME280Core::RESET_REG, 0xB6 }; return write(address, data, true); } @@ -48,7 +50,7 @@ namespace smooth::application::sensor { core::util::FixedBuffer status_reg; - bool res = read(address, STATUS_REG, status_reg); + bool res = read(address, BME280Core::STATUS_REG, status_reg); if (res) { @@ -59,217 +61,118 @@ namespace smooth::application::sensor return res; } - bool BME280::configure_sensor(SensorMode mode, - OverSampling humidity, - OverSampling pressure, - OverSampling temperature, - StandbyTimeMS standby, - FilterCoeff filter_coeff) + bool BME280::configure_sensor(BME280Core::SensorMode mode, + BME280Core::OverSampling humidity, + BME280Core::OverSampling pressure, + BME280Core::OverSampling temperature, + BME280Core::StandbyTimeMS standby, + BME280Core::FilterCoeff filter_coeff, + BME280Core::SpiInterface spi_interface) { - std::vector data; - - data.push_back(CTRL_HUM_REG); - data.push_back(humidity); - - data.push_back(CTRL_MEAS_REG); - auto meas_data = static_cast(temperature << 5); - meas_data = static_cast(meas_data | (pressure << 2)); - meas_data = static_cast(meas_data | mode); - data.push_back(meas_data); - - data.push_back(CONFIG_REG); - auto config_data = static_cast((standby << 5) | (filter_coeff << 2)); - data.push_back(config_data); - - return write(address, data, true); + std::vector datagram; + + bme280_core.get_configure_sensor_datagram(datagram, + mode, + humidity, + pressure, + temperature, + standby, + filter_coeff, + spi_interface); + + return write(address, datagram, true); } - bool BME280::read_configuration(SensorMode& mode, - OverSampling& humidity, - OverSampling& pressure, - OverSampling& temperature, - StandbyTimeMS& standby, - FilterCoeff& filter) + bool BME280::read_configuration(BME280Core::SensorMode& mode, + BME280Core::OverSampling& humidity, + BME280Core::OverSampling& pressure, + BME280Core::OverSampling& temperature, + BME280Core::StandbyTimeMS& standby, + BME280Core::FilterCoeff& filter, + BME280Core::SpiInterface& spi_interface) { core::util::FixedBuffer hum; core::util::FixedBuffer rest; core::util::FixedBuffer config; - bool res = read(address, CTRL_HUM_REG, hum); - res &= read(address, CTRL_MEAS_REG, rest); - res &= read(address, CONFIG_REG, config); + auto res = read(address, BME280Core::CTRL_HUM_REG, hum) + && read(address, BME280Core::CTRL_MEAS_REG, rest) + && read(address, BME280Core::CONFIG_REG, config); if (res) { - core::util::ByteSet h(hum[0]); - humidity = static_cast(h.get_bits_as_byte(0, 2)); - - core::util::ByteSet r(rest[0]); - mode = static_cast(r.get_bits_as_byte(0, 1)); - pressure = static_cast(r.get_bits_as_byte(2, 4)); - temperature = static_cast(r.get_bits_as_byte(5, 7)); - - core::util::ByteSet c(config[0]); - standby = static_cast(c.get_bits_as_byte(5, 7)); - filter = static_cast(c.get_bits_as_byte(2, 4)); + bme280_core.get_configuration_settings(hum[0], + rest[0], + config[0], + mode, + humidity, + pressure, + temperature, + standby, + filter, + spi_interface); } return res; } + // Read the trimming parameters bool BME280::read_trimming_parameters() { if (!trimming_read) { - uint8_t h4{}; - uint8_t h4_1{}; - uint8_t h5{}; - uint8_t h5_1{}; - - trimming_read = - read_16bit(DIG_T1_REG, trimming.dig_T1) - && read_16bit(DIG_T2_REG, trimming.dig_T2) - && read_16bit(DIG_T3_REG, trimming.dig_T3) - && read_16bit(DIG_P1_REG, trimming.dig_P1) - && read_16bit(DIG_P2_REG, trimming.dig_P2) - && read_16bit(DIG_P3_REG, trimming.dig_P3) - && read_16bit(DIG_P4_REG, trimming.dig_P4) - && read_16bit(DIG_P5_REG, trimming.dig_P5) - && read_16bit(DIG_P6_REG, trimming.dig_P6) - && read_16bit(DIG_P7_REG, trimming.dig_P7) - && read_16bit(DIG_P8_REG, trimming.dig_P8) - && read_16bit(DIG_P9_REG, trimming.dig_P9) - && read_8bit(DIG_H1_REG, trimming.dig_H1) - && read_16bit(DIG_H2_REG, trimming.dig_H2) - && read_8bit(DIG_H3_REG, trimming.dig_H3) - && read_8bit(DIG_H6_REG, trimming.dig_H6) - - // The following data is a bit tricky as it is split over multiple bytes. - // dig_H4: Bits 11:4 located in DIG_H4_REG, bits 3:0 located in DIG_H4_REG+1 - // dig_H5: bits 3:0 located in DIG_H5_REG[7:4], bits 11:4 in DIG_H5_REG+1 - - && read_8bit(DIG_H4_REG, h4) - && read_8bit(static_cast(DIG_H4_REG + 1), h4_1) - && read_8bit(DIG_H5_REG, h5) - && read_8bit(static_cast(DIG_H5_REG + 1), h5_1); - - // Adjust values so they are directly usable. - trimming.dig_H4 = static_cast((h4 << 4) | (h4_1 & 0xF)); - trimming.dig_H5 = static_cast((h5 >> 4) | (h5_1 << 4)); + core::util::FixedBuffer calibration_data; + core::util::FixedBuffer calib00_calib25_data; // 0x88-0xA1 + core::util::FixedBuffer calib26_calib32_data; // 0xE1-0xE7 + + trimming_read = read(address, BME280Core::CALIB00_REG, calib00_calib25_data) + && read(address, BME280Core::CALIB26_REG, calib26_calib32_data); + + if (trimming_read) + { + // copy 0x88-0x9F into calibration_data + for (size_t i = 0; i < 24; i++) + { + calibration_data[i] = calib00_calib25_data[i]; + } + + // copy 0xA1 but not 0xA0 into calibration_data + calibration_data[24] = calib00_calib25_data[25]; + + // copy 0xE1 to 0xE7 into calibration_data + for (size_t i = 0; i < 7; i++) + { + calibration_data[i + 25] = calib26_calib32_data[i]; + } + + bme280_core.populate_trimming_registers(calibration_data); + } + else + { + Log::error(TAG, "Problems reading trimming parameters"); + } } return trimming_read; } + // Read Temperature, Pressure and Humidity bool BME280::read_measurements(float& humidity, float& pressure, float& temperature) { - core::util::FixedBuffer measurement{}; + core::util::FixedBuffer measurement_data{}; humidity = 0; pressure = 0; temperature = 0; - bool res = read_trimming_parameters(); - res = res && read(address, PRESS_MSB_REG, measurement); + bool res = read_trimming_parameters() + && read(address, BME280Core::PRESS_MSB_REG, measurement_data); if (res) { - // Temperature data is at index: MSB: 3, LSB: 4, XLSB: 5 - auto temp = measurement[5] & 0x0F; - temp |= measurement[4] << 4; - temp |= measurement[3] << (4 + 8); - - temperature = static_cast(BME280_compensate_T_int32(temp) / 100.0); - - // Pressure data is at index: MSB: 0, LSB: 1, XLSB: 2 - int32_t press = measurement[2] & 0xF; - press |= measurement[1] << 4; - press |= measurement[0] << (4 + 8); - - pressure = static_cast(BME280_compensate_P_int64(press) / 256.0); - - // Humidity data is at index: MSB: 6, LSB: 7 - - int32_t hum = measurement[7]; - hum |= measurement[6] << 8; - - humidity = static_cast(BME280_compensate_H_int32(hum) / 1024); + bme280_core.get_measurements(measurement_data, humidity, pressure, temperature); } return res; } - - BME280::BME280_S32_t BME280::BME280_compensate_T_int32(BME280_S32_t adc_T) - { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" - BME280_S32_t var1, var2, T; - var1 = ((((adc_T >> 3) - ((BME280_S32_t)trimming.dig_T1 << 1))) - * ((BME280_S32_t)trimming.dig_T2)) >> 11; - - var2 = (((((adc_T >> 4) - ((BME280_S32_t)trimming.dig_T1)) - * ((adc_T >> 4) - ((BME280_S32_t)trimming.dig_T1))) >> 12) - * ((BME280_S32_t)trimming.dig_T3)) >> 14; - t_fine = var1 + var2; - T = (t_fine * 5 + 128) >> 8; - - return T; -#pragma GCC diagnostic pop - } - - BME280::BME280_U32_t BME280::BME280_compensate_P_int64(BME280_S32_t adc_P) - { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" - BME280_S64_t var1, var2, p; - var1 = ((BME280_S64_t)t_fine) - 128000; - var2 = var1 * var1 * (BME280_S64_t)trimming.dig_P6; - var2 = var2 + ((var1 * (BME280_S64_t)trimming.dig_P5) << 17); - var2 = var2 + (((BME280_S64_t)trimming.dig_P4) << 35); - var1 = ((var1 * var1 * (BME280_S64_t)trimming.dig_P3) >> 8) - + ((var1 * (BME280_S64_t)trimming.dig_P2) << 12); - var1 = (((((BME280_S64_t)1) << 47) + var1)) * ((BME280_S64_t)trimming.dig_P1) >> 33; - - if (var1 == 0) - { - return 0; // avoid exception caused by division by zero - } - - p = 1048576 - adc_P; - p = (((p << 31) - var2) * 3125) / var1; - var1 = (((BME280_S64_t)trimming.dig_P9) * (p >> 13) * (p >> 13)) >> 25; - var2 = (((BME280_S64_t)trimming.dig_P8) * p) >> 19; - p = ((p + var1 + var2) >> 8) + (((BME280_S64_t)trimming.dig_P7) << 4); - - return (BME280_U32_t)p; -#pragma GCC diagnostic pop - } - - BME280::BME280_U32_t BME280::BME280_compensate_H_int32(BME280_S32_t adc_H) - { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" - BME280_S32_t v_x1_u32r; - v_x1_u32r = (t_fine - ((BME280_S32_t)76800)); - v_x1_u32r = (((((adc_H << 14) - (((BME280_S32_t)trimming.dig_H4) << 20) - - (((BME280_S32_t)trimming.dig_H5) - * v_x1_u32r)) - + ((BME280_S32_t)16384)) >> 15) * ( - ((((((v_x1_u32r * ((BME280_S32_t)trimming.dig_H6)) >> 10) * (((v_x1_u32r - * ((BME280_S32_t) - trimming.dig_H3)) - >> 11) - + ((BME280_S32_t)32768))) - >> 10) - + ((BME280_S32_t)2097152)) - * ((BME280_S32_t)trimming.dig_H2) + 8192) >> 14)); - v_x1_u32r = (v_x1_u32r - - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((BME280_S32_t)trimming.dig_H1)) - >> 4)); - v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r); - v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r); - - return (BME280_U32_t)(v_x1_u32r >> 12); -#pragma GCC diagnostic pop - } } diff --git a/lib/smooth/application/io/spi/BME280Core.cpp b/lib/smooth/application/io/spi/BME280Core.cpp new file mode 100644 index 00000000..4f00c7bb --- /dev/null +++ b/lib/smooth/application/io/spi/BME280Core.cpp @@ -0,0 +1,212 @@ +/* +Smooth - A C++ framework for embedded programming on top of Espressif's ESP-IDF +Copyright 2019 Per Malmberg (https://gitbub.com/PerMalmberg) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#include "smooth/core/util/ByteSet.h" +#include "smooth/application/io/spi/BME280Core.h" + +using namespace smooth::core; + +namespace smooth::application::sensor +{ + // Constructor + BME280Core::BME280Core() + { + } + + // Get config_sensor datagram that contains register addresses and data that will be used to configure the BME280 + void BME280Core::get_configure_sensor_datagram(std::vector& datagram, + SensorMode mode, + OverSampling humidity, + OverSampling pressure, + OverSampling temperature, + StandbyTimeMS standby, + FilterCoeff filter_coeff, + SpiInterface spi_interface) + { + // Register 0xF2 + datagram.push_back(CTRL_HUM_REG); + datagram.push_back(humidity); + + // Register 0xF4 + datagram.push_back(CTRL_MEAS_REG); + auto meas_data = temperature << 5; + meas_data = meas_data | (pressure << 2); + meas_data = (meas_data | mode); + datagram.push_back(static_cast(meas_data)); + + // Register 0xF5 + datagram.push_back(CONFIG_REG); + auto config_data = (standby << 5) | (filter_coeff << 2) | spi_interface; + datagram.push_back(static_cast(config_data)); + } + + // Get the current configuration of BME280 + void BME280Core::get_configuration_settings(uint8_t ctrl_hum_data, + uint8_t ctrl_meas_data, + uint8_t config_data, + SensorMode& mode, + OverSampling& humidity, + OverSampling& pressure, + OverSampling& temperature, + StandbyTimeMS& standby, + FilterCoeff& filter, + SpiInterface& spi_interface) + { + util::ByteSet h(ctrl_hum_data); + humidity = static_cast(h.get_bits_as_byte(0, 2)); + + util::ByteSet r(ctrl_meas_data); + mode = static_cast(r.get_bits_as_byte(0, 1)); + pressure = static_cast(r.get_bits_as_byte(2, 4)); + temperature = static_cast(r.get_bits_as_byte(5, 7)); + + util::ByteSet c(config_data); + standby = static_cast(c.get_bits_as_byte(5, 7)); + filter = static_cast(c.get_bits_as_byte(2, 4)); + spi_interface = static_cast(config_data & 0x01); + } + + // Populate the trimming registers that will be used in calculation for compensated measurements + void BME280Core::populate_trimming_registers(core::util::FixedBuffer& calibration_data) + { + trimming.dig_T1 = static_cast(calibration_data[1] << 8 | calibration_data[0]); + trimming.dig_T2 = static_cast(calibration_data[3] << 8 | calibration_data[2]); + trimming.dig_T3 = static_cast(calibration_data[5] << 8 | calibration_data[4]); + trimming.dig_P1 = static_cast(calibration_data[7] << 8 | calibration_data[6]); + trimming.dig_P2 = static_cast(calibration_data[9] << 8 | calibration_data[8]); + trimming.dig_P3 = static_cast(calibration_data[11] << 8 | calibration_data[10]); + trimming.dig_P4 = static_cast(calibration_data[13] << 8 | calibration_data[12]); + trimming.dig_P5 = static_cast(calibration_data[15] << 8 | calibration_data[14]); + trimming.dig_P6 = static_cast(calibration_data[17] << 8 | calibration_data[16]); + trimming.dig_P7 = static_cast(calibration_data[19] << 8 | calibration_data[18]); + trimming.dig_P8 = static_cast(calibration_data[21] << 8 | calibration_data[20]); + trimming.dig_P9 = static_cast(calibration_data[23] << 8 | calibration_data[22]); + trimming.dig_H1 = calibration_data[24]; + trimming.dig_H2 = static_cast(calibration_data[26] << 8 | calibration_data[25]); + trimming.dig_H3 = calibration_data[27]; + trimming.dig_H4 = static_cast((calibration_data[28] << 4) | (calibration_data[29] & 0x0F)); // 0xE4<<4 + // | 0xE5&0x0F + trimming.dig_H5 = static_cast((calibration_data[30] << 4) | (calibration_data[29] >> 4)); // 0xE6<<4 + // | 0xE5>>4 + trimming.dig_H6 = static_cast(calibration_data[31]); + } + + // Get the compensated measurements + void BME280Core::get_measurements(core::util::FixedBuffer& measurement_data, + float& humidity, float& pressure, float& temperature) + { + // Temperature data is at index: MSB: 3, LSB: 4, XLSB: 5 + auto temp = measurement_data[5] & 0x0F; + temp |= measurement_data[4] << 4; + temp |= measurement_data[3] << (4 + 8); + + temperature = static_cast(BME280_compensate_T_int32(temp) / 100.0); + + // Pressure data is at index: MSB: 0, LSB: 1, XLSB: 2 + int32_t press = measurement_data[2] & 0xF; + press |= measurement_data[1] << 4; + press |= measurement_data[0] << (4 + 8); + + pressure = static_cast(BME280_compensate_P_int64(press) / 256.0); + + // Humidity data is at index: MSB: 6, LSB: 7 + int32_t hum = measurement_data[7]; + hum |= measurement_data[6] << 8; + + humidity = static_cast(BME280_compensate_H_int32(hum) / 1024); + } + + // The following calculation methods are based on those in the datasheet at + // https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BME280_DS001-11.pdf + // as of 2017-08-20. The revision of the code is 1.1 according to that document. + + // Compensation formula for TEMPERATURE + BME280Core::BME280_S32_t BME280Core::BME280_compensate_T_int32(BME280_S32_t adc_T) + { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" + BME280_S32_t var1, var2, T; + var1 = ((((adc_T >> 3) - ((BME280_S32_t)trimming.dig_T1 << 1))) + * ((BME280_S32_t)trimming.dig_T2)) >> 11; + + var2 = (((((adc_T >> 4) - ((BME280_S32_t)trimming.dig_T1)) + * ((adc_T >> 4) - ((BME280_S32_t)trimming.dig_T1))) >> 12) + * ((BME280_S32_t)trimming.dig_T3)) >> 14; + t_fine = var1 + var2; + T = (t_fine * 5 + 128) >> 8; + + return T; +#pragma GCC diagnostic pop + } + + // Compensation formula for PRESSURE + BME280Core::BME280_U32_t BME280Core::BME280_compensate_P_int64(BME280_S32_t adc_P) + { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" + BME280_S64_t var1, var2, p; + var1 = ((BME280_S64_t)t_fine) - 128000; + var2 = var1 * var1 * (BME280_S64_t)trimming.dig_P6; + var2 = var2 + ((var1 * (BME280_S64_t)trimming.dig_P5) << 17); + var2 = var2 + (((BME280_S64_t)trimming.dig_P4) << 35); + var1 = ((var1 * var1 * (BME280_S64_t)trimming.dig_P3) >> 8) + + ((var1 * (BME280_S64_t)trimming.dig_P2) << 12); + var1 = (((((BME280_S64_t)1) << 47) + var1)) * ((BME280_S64_t)trimming.dig_P1) >> 33; + + if (var1 == 0) + { + return 0; // avoid exception caused by division by zero + } + + p = 1048576 - adc_P; + p = (((p << 31) - var2) * 3125) / var1; + var1 = (((BME280_S64_t)trimming.dig_P9) * (p >> 13) * (p >> 13)) >> 25; + var2 = (((BME280_S64_t)trimming.dig_P8) * p) >> 19; + p = ((p + var1 + var2) >> 8) + (((BME280_S64_t)trimming.dig_P7) << 4); + + return (BME280_U32_t)p; +#pragma GCC diagnostic pop + } + + // Compensation formula for HUMIDITY + BME280Core::BME280_U32_t BME280Core::BME280_compensate_H_int32(BME280_S32_t adc_H) + { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" + BME280_S32_t v_x1_u32r; + v_x1_u32r = (t_fine - ((BME280_S32_t)76800)); + v_x1_u32r = (((((adc_H << 14) - (((BME280_S32_t)trimming.dig_H4) << 20) + - (((BME280_S32_t)trimming.dig_H5) + * v_x1_u32r)) + + ((BME280_S32_t)16384)) >> 15) * ( + ((((((v_x1_u32r * ((BME280_S32_t)trimming.dig_H6)) >> 10) * (((v_x1_u32r + * ((BME280_S32_t) + trimming.dig_H3)) + >> 11) + + ((BME280_S32_t)32768))) + >> 10) + + ((BME280_S32_t)2097152)) + * ((BME280_S32_t)trimming.dig_H2) + 8192) >> 14)); + v_x1_u32r = (v_x1_u32r + - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((BME280_S32_t)trimming.dig_H1)) + >> 4)); + v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r); + v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r); + + return (BME280_U32_t)(v_x1_u32r >> 12); +#pragma GCC diagnostic pop + } +} diff --git a/lib/smooth/application/io/spi/BME280SPI.cpp b/lib/smooth/application/io/spi/BME280SPI.cpp new file mode 100644 index 00000000..ec84705e --- /dev/null +++ b/lib/smooth/application/io/spi/BME280SPI.cpp @@ -0,0 +1,266 @@ +/* +Smooth - A C++ framework for embedded programming on top of Espressif's ESP-IDF +Copyright 2019 Per Malmberg (https://gitbub.com/PerMalmberg) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#include +#include "smooth/core/logging/log.h" +#include "smooth/application/io/spi/BME280SPI.h" + +using namespace smooth::core::logging; +using namespace smooth::core; +using namespace smooth::core::io::spi; + +namespace smooth::application::sensor +{ + static const char* TAG = "BME280SPI"; + + BME280SPI::BME280SPI(std::mutex& guard, + gpio_num_t chip_select_pin, + uint8_t spi_command_bits, + uint8_t spi_address_bits, + uint8_t bits_between_address_and_data_phase, + uint8_t spi_mode, + uint8_t spi_positive_duty_cycle, + uint8_t spi_cs_ena_posttrans, + int spi_clock_speed_hz, + uint32_t spi_device_flags, + int spi_queue_size, + bool use_pre_transaction_callback, + bool use_post_transaction_callback) + : SPIDevice(guard, + spi_command_bits, + spi_address_bits, + bits_between_address_and_data_phase, + spi_mode, + spi_positive_duty_cycle, + spi_cs_ena_posttrans, + spi_clock_speed_hz, + spi_device_flags, + spi_queue_size, + use_pre_transaction_callback, + use_post_transaction_callback), + + cs_pin(chip_select_pin) + { + } + + bool BME280SPI::init(spi_host_device_t host) + { + auto res = initialize(host, cs_pin); + + return res; + } + + bool BME280SPI::read(const uint8_t bme280_reg, uint8_t* rxdata, size_t length) + { + std::lock_guard lock(get_guard()); + spi_transaction_t trans; + std::memset(&trans, 0, sizeof(trans)); //Zero out the transaction + trans.rx_buffer = rxdata; + trans.rxlength = 8 * length; + trans.length = 8 * length; + trans.tx_buffer = &bme280_reg; + + auto res = polling_write(trans); + + return res; + } + + bool BME280SPI::write(const uint8_t* txdata, size_t length) + { + std::lock_guard lock(get_guard()); + spi_transaction_t trans; + std::memset(&trans, 0, sizeof(trans)); //Zero out the transaction + trans.rx_buffer = NULL; + trans.length = 8 * length; + trans.tx_buffer = const_cast(txdata); + + auto res = polling_write(trans); + + return res; + } + + uint8_t BME280SPI::read_id() + { + auto res = read(BME280Core::ID_REG, rxdata.data(), 2); + + return res ? rxdata[1] : 0; + } + + bool BME280SPI::reset() + { + SpiDmaFixedBuffer txdata; + + // for spi write bit 7 of register address must be zero + // modify 0xE0 to 0x60; + txdata[0] = BME280Core::RESET_REG& 0x7F; + + // magic number to the reset device + txdata[1] = 0xB6; + + return write(txdata.data(), 2); + } + + bool BME280SPI::read_status(bool& is_measuring, bool& updating_from_nvm) + { + auto res = read(BME280Core::STATUS_REG, rxdata.data(), 2); + + if (res) + { + is_measuring = rxdata[1] & 0x4; + updating_from_nvm = rxdata[1] & 0x1; + } + + return res; + } + + bool BME280SPI::configure_sensor(BME280Core::SensorMode mode, + BME280Core::OverSampling humidity, + BME280Core::OverSampling pressure, + BME280Core::OverSampling temperature, + BME280Core::StandbyTimeMS standby, + BME280Core::FilterCoeff filter_coeff, + BME280Core::SpiInterface spi_interface) + { + std::vector datagram; + + bme280_core.get_configure_sensor_datagram(datagram, + mode, + humidity, + pressure, + temperature, + standby, + filter_coeff, + spi_interface); + + // Since we chose to use DMA on this SPI Device use SpiDmaFixedBuffer for the tx_buffer in spi_transfer + SpiDmaFixedBuffer txdata; + + // for spi write the bit in position b7 of register address must be zero + txdata[0] = datagram.at(0) & 0x7F; // modify 0xF2 to 0x72 + txdata[1] = datagram.at(1); // keep data the same + txdata[2] = datagram.at(2) & 0x7F; // modify 0xF4 to 0x74 + txdata[3] = datagram.at(3); // keep data the same + txdata[4] = datagram.at(4) & 0x7F; // modify 0xF5 to 0x75 + txdata[5] = datagram.at(5); // keep data the same + + return write(txdata.data(), 6); + } + + bool BME280SPI::read_configuration(BME280Core::SensorMode& mode, + BME280Core::OverSampling& humidity, + BME280Core::OverSampling& pressure, + BME280Core::OverSampling& temperature, + BME280Core::StandbyTimeMS& standby, + BME280Core::FilterCoeff& filter, + BME280Core::SpiInterface& spi_interface) + { + // Since we chose to use DMA on this SPI Device use SpiDmaFixedBuffer for the rx_buffer in spi_transfer + SpiDmaFixedBuffer ctrl_hum_data; + SpiDmaFixedBuffer ctrl_meas_data; + SpiDmaFixedBuffer config_data; + + auto res = read(BME280Core::CTRL_HUM_REG, ctrl_hum_data.data(), 2) + && read(BME280Core::CTRL_MEAS_REG, ctrl_meas_data.data(), 2) + && read(BME280Core::CONFIG_REG, config_data.data(), 2); + + if (res) + { + // Since we are using 4-line spi (full duplex) the sck clocks both data out + // and data in at the same time. The first byte going out is the register we want + // to read and so the first byte received is garbage or dummy byte. So we + // will pass the 2nd byte received to get_configuration_settings. + bme280_core.get_configuration_settings(ctrl_hum_data[1], + ctrl_meas_data[1], + config_data[1], + mode, + humidity, + pressure, + temperature, + standby, + filter, + spi_interface); + } + + return res; + } + + bool BME280SPI::read_trimming_parameters() + { + if (!trimming_read) + { + // Since we chose to use DMA on this SPI Device use SpiDmaFixedBuffer for the rx_buffer in spi_transfer + SpiDmaFixedBuffer calib00_calib25_data; // 0x88-0xA1 + SpiDmaFixedBuffer calib26_calib32_data; // 0xE1-0xE7 + core::util::FixedBuffer calibration_data; + + trimming_read = read(BME280Core::CALIB00_REG, calib00_calib25_data.data(), 27) + && read(BME280Core::CALIB26_REG, calib26_calib32_data.data(), 8); + + if (trimming_read) + { + // Since we are using 4-line spi (full duplex) the sck clocks both data out + // and data in at the same time. The first byte going out is the register we want + // to read and so the first byte received is garbage or dummy byte. So we + // don't want to copy the dummy byte but copy 0x88-0x9F into calibration_data + for (size_t i = 0; i < 24; i++) + { + calibration_data[i] = calib00_calib25_data[i + 1]; + } + + // copy 0xA1 but not 0xA0 into calibration_data + calibration_data[24] = calib00_calib25_data[25]; + + // don't copy dummy byte but copy 0xE1 to 0xE7 into calibration_data + for (size_t i = 0; i < 7; i++) + { + calibration_data[i + 25] = calib26_calib32_data[i + 1]; + } + + bme280_core.populate_trimming_registers(calibration_data); + } + else + { + Log::error(TAG, "Problems reading trimming parameters"); + } + } + + return trimming_read; + } + + bool BME280SPI::read_measurements(float& humidity, float& pressure, float& temperature) + { + smooth::core::util::FixedBuffer measurement_data; + + bool res = read_trimming_parameters() + && read(BME280Core::PRESS_MSB_REG, rx_measurement_data.data(), 9); + + if (res) + { + // Since we are using 4-line spi (full duplex) the sck clocks both data out + // and data in at the same time. The first byte going out is the register we want + // to read and so the first byte received is garbage or dummy byte. So we + // don't want to copy the dummy byte but copy actual data into measurement_data + for (size_t i = 0; i < 8; i++) + { + measurement_data[i] = rx_measurement_data[i + 1]; + } + + bme280_core.get_measurements(measurement_data, humidity, pressure, temperature); + } + + return res; + } +} diff --git a/lib/smooth/core/io/spi/Master.cpp b/lib/smooth/core/io/spi/Master.cpp index e116bb16..ed76776e 100644 --- a/lib/smooth/core/io/spi/Master.cpp +++ b/lib/smooth/core/io/spi/Master.cpp @@ -14,7 +14,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "smooth/core/io/spi/Master.h" #include "smooth/core/logging/log.h" @@ -22,12 +21,15 @@ using namespace smooth::core::logging; namespace smooth::core::io::spi { + static constexpr const char* log_tag = "SPIMaster"; + Master::Master( spi_host_device_t host, SPI_DMA_Channel dma_channel, gpio_num_t mosi, gpio_num_t miso, gpio_num_t clock, + int transfer_size, gpio_num_t quadwp_io_num, gpio_num_t quadhd_io_num ) @@ -37,39 +39,47 @@ namespace smooth::core::io::spi bus_config.mosi_io_num = mosi; bus_config.miso_io_num = miso; bus_config.sclk_io_num = clock; - bus_config.quadwp_io_num = quadwp_io_num; bus_config.quadhd_io_num = quadhd_io_num; + bus_config.max_transfer_sz = transfer_size; + } + + Master::~Master() + { + deinitialize(); } bool Master::initialize() { std::lock_guard lock(guard); + do_initialization(); + return initialized; + } + + void Master::do_initialization() + { if (!initialized) { - esp_err_t res = spi_bus_initialize(host, &bus_config, dma_channel); + initialized = spi_bus_initialize(host, &bus_config, dma_channel) == ESP_OK; - if (res == ESP_ERR_INVALID_ARG) + if (!initialized) { - Log::error(log_tag, "Invalid configuration"); - } - else if (res == ESP_ERR_INVALID_STATE) - { - Log::error(log_tag, "Host already is in use"); - } - else if (res == ESP_ERR_NO_MEM) - { - Log::error(log_tag, "Out of memory"); + Log::error(log_tag, "Initialization failed"); } else { Log::verbose(log_tag, "SPI initialized, Host {}, DMA {}", host, dma_channel); } - - initialized = res == ESP_OK; } + } - return initialized; + void Master::deinitialize() + { + if (initialized) + { + initialized = false; + spi_bus_free(host); + } } } diff --git a/lib/smooth/core/io/spi/SPIDevice.cpp b/lib/smooth/core/io/spi/SPIDevice.cpp index 34d693cc..69039585 100644 --- a/lib/smooth/core/io/spi/SPIDevice.cpp +++ b/lib/smooth/core/io/spi/SPIDevice.cpp @@ -14,7 +14,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #include "smooth/core/io/spi/SPIDevice.h" #include "smooth/core/logging/log.h" @@ -23,32 +22,33 @@ using namespace smooth::core::logging; namespace smooth::core::io::spi { static constexpr const char* log_tag = "SPIDevice"; + static constexpr const std::chrono::milliseconds timeout(1000); SPIDevice::SPIDevice( std::mutex& guard, - uint8_t command_bits, - uint8_t address_bits, + uint8_t spi_command_bits, + uint8_t spi_address_bits, uint8_t bits_between_address_and_data_phase, uint8_t spi_mode, - uint8_t positive_duty_cycle, - uint8_t cs_ena_posttrans, - int clock_speed_hz, - uint32_t flags, - int queue_size, + uint8_t spi_positive_duty_cycle, + uint8_t spi_cs_ena_posttrans, + int spi_clock_speed_hz, + uint32_t spi_device_flags, + int spi_queue_size, bool use_pre_transaction_callback, bool use_post_transaction_callback) : guard(guard) { - config.command_bits = command_bits; - config.address_bits = address_bits; + config.command_bits = spi_command_bits; + config.address_bits = spi_address_bits; config.dummy_bits = bits_between_address_and_data_phase; config.mode = spi_mode; - config.duty_cycle_pos = positive_duty_cycle; + config.duty_cycle_pos = spi_positive_duty_cycle; config.cs_ena_pretrans = 0; // Only for half-duplex - config.cs_ena_posttrans = cs_ena_posttrans; - config.clock_speed_hz = clock_speed_hz; - config.flags = flags; - config.queue_size = queue_size; + config.cs_ena_posttrans = spi_cs_ena_posttrans; + config.clock_speed_hz = spi_clock_speed_hz; + config.flags = spi_device_flags; + config.queue_size = spi_queue_size; if (use_pre_transaction_callback) { @@ -65,6 +65,10 @@ namespace smooth::core::io::spi { if (dev != nullptr) { + // make sure no transactions are in flight + wait_for_transaction_to_finish(); + + // remove device from bus spi_bus_remove_device(dev); } } @@ -95,6 +99,7 @@ namespace smooth::core::io::spi { // Attach ourselves as the user-data transaction.user = this; + auto res = spi_device_transmit(dev, &transaction); if (res != ESP_OK) @@ -105,13 +110,58 @@ namespace smooth::core::io::spi return res == ESP_OK; } + bool SPIDevice::polling_write(spi_transaction_t& transaction) + { + // Attach ourselves as the user-data + transaction.user = this; + + auto res = spi_device_polling_transmit(dev, &transaction); + + if (res != ESP_OK) + { + Log::error(log_tag, "polling_write() failed"); + } + + return res == ESP_OK; + } + + bool SPIDevice::queue_transaction(spi_transaction_t& transaction) + { + // Attach ourselves as the user-data + transaction.user = this; + + auto res = spi_device_queue_trans(dev, &transaction, to_tick(timeout)); + + if (res != ESP_OK) + { + Log::error(log_tag, "queued_transaction() failed"); + } + + return res == ESP_OK; + } + + bool SPIDevice::wait_for_transaction_to_finish() + { + spi_transaction_t* rtrans; + + auto res = spi_device_get_trans_result(dev, &rtrans, to_tick(timeout)); + + if (res != ESP_OK) + { + Log::error(log_tag, "wait_for_transaction_to_finish() failed"); + } + + return res == ESP_OK; + } + void SPIDevice::pre_transmission_callback(spi_transaction_t* trans) { auto device = reinterpret_cast(trans->user); if (device != nullptr) { - device->pre_transmission_action(trans); + //device->pre_transmission_action(trans); + device->pre_transmission_action(); } } @@ -121,7 +171,8 @@ namespace smooth::core::io::spi if (device != nullptr) { - device->post_transmission_action(trans); + //device->post_transmission_action(trans); + device->post_transmission_action(); } } } diff --git a/lib/smooth/include/smooth/application/display/ILI9341.h b/lib/smooth/include/smooth/application/display/ILI9341.h new file mode 100644 index 00000000..35eaa30f --- /dev/null +++ b/lib/smooth/include/smooth/application/display/ILI9341.h @@ -0,0 +1,160 @@ +/* +Smooth - A C++ framework for embedded programming on top of Espressif's ESP-IDF +Copyright 2019 Per Malmberg (https://gitbub.com/PerMalmberg) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/************************************************************************************ + SPECIAL NOTES SPECIAL NOTES SPECIAL NOTES + + NOTE 1: This driver is written to work with jumpers on the ILI9341 set to the + following: IM0=0, IM1=1, IM2=1, IM3=1. This jumper setting configures the + ILI9341 to operates as a 4-wire 8-bit data serial interface II see page 26 + and 38 of datasheet for more details. + + NOTE 2: This driver implements both reading and writing to display. Read is used + for reading parameters from certain registers. The maximum spi clock speed is + 16MHz but data sheet recommends 10MHz. + + NOTE 3: Most applications will not need the ability to read regsiters and are + only writing to the display. In this case MISO can be set to GPIO_NUM_NC. Also + the spi clock speed can be increased to either 26MHz or 40MHz. The 2 displays I + tested this driver with operated at 40MHz with no problem. + ************************************************************************************/ +#pragma once + +#include +#include +#include "smooth/core/io/Output.h" +#include "smooth/core/io/spi/Master.h" +#include "smooth/core/io/spi/SPIDevice.h" + +namespace smooth::application::display +{ + class ILI9341 : public core::io::spi::SPIDevice + { + public: + /// The constructor + ILI9341(std::mutex& guard, + gpio_num_t chip_select_pin, + gpio_num_t data_command_pin, + gpio_num_t reset_pin, + gpio_num_t back_light_pin, + uint8_t spi_command_bits, + uint8_t spi_address_bits, + uint8_t bits_between_address_and_data_phase, + uint8_t spi_mode, + uint8_t spi_positive_duty_cycle, + uint8_t spi_cs_ena_posttrans, + int spi_clock_speed_hz, + uint32_t spi_device_flags, + int spi_queue_size, + bool use_pre_transaction_callback, + bool use_post_transaction_callback); + + /// Set backlight + /// Turn backlight either ON or OFF + void set_back_light(bool on) + { + on ? backlight_pin.set() : backlight_pin.clr(); + } + + /// Initialize the display + /// \param host The SPI host either VSPI or HSPI + /// \return true on success, false on failure + bool init(spi_host_device_t host); + + /// Set the rotation of display + /// Set the rotation of display using the MADCTL value see page 127 of datasheet + /// \param madctl_value The MADCTL value + /// \return true on success, false on failure + bool set_rotation(uint8_t madctl_value); + + /// Reset the display + void reset_display(); + + /// Send init commands + /// Send a sequence of commands and data to the display to perform intialization + /// \return true on success, false on failure + bool send_init_cmds(); + + /// LCD write command + /// \param cmd The command (register address) to write + /// \return true on success, false on failure + bool lcd_wr_cmd(const uint8_t cmd); + + /// LCD write data + /// \param data The pointer to the first byte in the data + /// \param length The number of bytes in the data + /// \return true on success, false on failure + bool lcd_wr_data(const uint8_t* data, size_t length); + + /// Send lines + /// Sends color data to display starting at position x1,y1 and ending at position x2,y2 + /// \param x1 The top left corner x position + /// \param y1 The top left corner y position + /// \param x2 The bottom right corner x position + /// \param y2 The bottom right corner y position + /// \param data The pointer to the first byte in the data (color data) + /// \param length The number of bytes in the data + /// \return true on success, false on failure + bool send_lines(int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint8_t* data, size_t length); + + /// Wait for send lines to finish; + /// Waits for all SPI transactions in send lines to complete + /// \return true on success, false on failure + bool wait_for_send_lines_to_finish(); + + /// Read parameters + /// \param cmd The command (address register) + /// \param data The address of the container to hold the parameter data + /// \param param_count The number of parameters to be read + /// \return true on success, false on failure + bool read_params(uint8_t cmd, std::vector& data, uint32_t param_count); + + /// Read + /// \param cmd The command or register address to read + /// \param rxdata The pointer to the first byte location of the receive data container + /// \param length The number of bytes to raed + /// \return true on success, false on failure + bool read(uint8_t cmd, uint8_t* rxdata, size_t length); + + private: + /// Pre Transmission Action + /// The operations that need to be performed before the spi transaction is started + void pre_transmission_action() override + { + dc_pin.set(dc_pretrans_pin_states.at(current_transaction)); + cs_pin.set(cs_pretrans_pin_states.at(current_transaction)); + } + + /// Post Transmission Action + /// The operations that need to be performed after the spi transaction has ended + void post_transmission_action() override + { + cs_pin.set(cs_posttrans_pin_states.at(current_transaction)); + current_transaction++; + } + + smooth::core::io::Output reset_pin; + smooth::core::io::Output backlight_pin; + smooth::core::io::Output dc_pin; + smooth::core::io::Output cs_pin; + + std::array dc_pretrans_pin_states; + std::array cs_pretrans_pin_states; + std::array cs_posttrans_pin_states; + std::size_t current_transaction; + }; +} diff --git a/lib/smooth/include/smooth/application/display/ILI9341_init_cmds.h b/lib/smooth/include/smooth/application/display/ILI9341_init_cmds.h new file mode 100644 index 00000000..b3bbdcd5 --- /dev/null +++ b/lib/smooth/include/smooth/application/display/ILI9341_init_cmds.h @@ -0,0 +1,95 @@ +/* +Smooth - A C++ framework for embedded programming on top of Espressif's ESP-IDF +Copyright 2019 Per Malmberg (https://gitbub.com/PerMalmberg) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#pragma once + +#include +#include + +namespace smooth::application::display +{ + typedef struct LcdInitCmdType + { + uint8_t cmd; + uint8_t data[16]; + uint8_t databytes; //Num of bytes in data; bit 7 = delay after set; 0xFF = end of cmds. + } lcd_init_cmd_t; + + // Command sequence to initialize ili9341. A command sequence contains a 3 items; + // (1) a command byte, (2) the data to be sent and (3) the number of bytes in the data. + // Note 1: Memory Access Control: + // M5Stack uses 0x08=landscape 0x68=portrait; + // Adafruit 2.8/3.2 Display uses 0x28=landscape 0x48=portrait; + // If using LittlevGL make sure lv_conf.h is configured for correct display rotation + // Note 2: Pixel Format Set: 0x55 = 16bit, 0x66 = 18bit; also change lv_conf.h + static constexpr const std::array ili_init_cmds = + { { + { 0xEF, { 0x03, 0x80, 0X02 }, 3 }, // command sequence number 0 + { 0xCF, { 0x00, 0xC1, 0X30 }, 3 }, // command sequence number 1 + { 0xED, { 0x64, 0x03, 0X12, 0X81 }, 4 }, + { 0xE8, { 0x85, 0x00, 0x78 }, 3 }, + { 0xCB, { 0x39, 0x2C, 0x00, 0x34, 0x02 }, 5 }, + { 0xF7, { 0x20 }, 1 }, + { 0xEA, { 0x00, 0x00 }, 2 }, + { 0xC0, { 0x23 }, 1 }, //Power control + { 0xC1, { 0x10 }, 1 }, //Power control + { 0xC5, { 0x3E, 0x28 }, 2 }, //VCOM control + { 0xC7, { 0x86 }, 1 }, //VCOM control + { 0x36, { 0x48 }, 1 }, //Memory Access Control See Note 1 above //0xA8=M5Stack + { 0x3A, { 0x55 }, 1 }, //Pixel Format Set See Note 2 above + { 0xB1, { 0x00, 0x13 }, 2 }, // 0x18 79Hz, 0x1B default 70Hz, 0x13 100Hz + { 0xB6, { 0x08, 0x82, 0x27 }, 3 }, + { 0xF2, { 0x00 }, 1 }, + { 0x26, { 0x01 }, 1 }, + { 0xE0, { 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0X37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00 }, 15 }, + { 0XE1, { 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F }, 15 }, + { 0x11, { 0 }, 0x80 }, // Sleep Out cmd plus delay + { 0x29, { 0 }, 0x80 }, // Display on cmd plus delay + { 0, { 0 }, 0xff }, // End of sequence // command sequence number 21 + } }; + +/* + // another sequence of commands that also works..... + static constexpr const std::array ili_init_cmds = + {{ + { 0x28, {0x00}, 0 }, // command sequence number 0 + { 0xCF, {0x00, 0x83, 0x30}, 3}, + { 0xED, {0x64, 0x03, 0x12, 0x81}, 4 }, + { 0xE8, {0x85, 0x01, 0x79}, 3 }, + { 0xCB, {0x39, 0x2C, 0x00, 0x34, 0x02}, 5}, + { 0xF7, {0x20}, 1 }, + { 0xEA, {0x00, 0x00}, 2 }, + { 0xC0, {0x26}, 1 }, //Power control + { 0xC1, {0x11}, 1 }, //Power control + { 0xC5, {0x35, 0x3E}, 2 }, //VCOM control + { 0xC7, {0xBE}, 1 }, //VCOM control + { 0x36, {0x48}, 1 }, //Memory Access Control See Note 1 above //0x68 + { 0x3A, {0x55}, 1 }, //Pixel Format Set See Note 2 above + { 0xB1, {0x00, 0x1B}, 2 }, + { 0xF2, {0x08}, 1 }, + { 0x26, {0x01}, 1 }, + { 0xE0, {0x1F, 0x1A, 0x18, 0x0A, 0x0F, 0x06, 0x45, 0x87, 0X32, 0x0A, 0x07, 0x02, 0x07, 0x05, 0x00}, 15 }, + { 0XE1, {0x00, 0x25, 0x27, 0x05, 0x10, 0x09, 0x3A, 0x78, 0x4D, 0x05, 0x18, 0x0D, 0x38, 0x3A, 0x1F}, 15 }, + { 0x2A, {0x00, 0x00, 0x00, 0xEF}, 4 }, + { 0x2B, {0x00, 0x00, 0x01, 0x3F}, 4 }, + { 0xB7, {0x07}, 1 }, + { 0xB6, {0x0A, 0x82, 0x27, 0x00}, 4}, + { 0x11, {0}, 0x80 }, // Sleep Out cmd plus delay + { 0x29, {0}, 0x80 }, // Display on cmd plus delay + { 0, {0}, 0xff }, // End of sequence // command sequence number 24 + }}; +*/ +} diff --git a/lib/smooth/include/smooth/application/io/i2c/BME280.h b/lib/smooth/include/smooth/application/io/i2c/BME280.h index 17e67e5b..b6317a99 100644 --- a/lib/smooth/include/smooth/application/io/i2c/BME280.h +++ b/lib/smooth/include/smooth/application/io/i2c/BME280.h @@ -14,11 +14,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - #pragma once #include #include "smooth/core/io/i2c/I2CMasterDevice.h" +#include "smooth/application/io/spi/BME280Core.h" #include "smooth/core/util/FixedBuffer.h" namespace smooth::application::sensor @@ -30,44 +30,6 @@ namespace smooth::application::sensor public: BME280(i2c_port_t port, uint8_t address, std::mutex& guard); - enum SensorMode - { - Sleep = 0x00, - Forced = 0x01, - Normal = 0x03 - }; - - enum OverSampling - { - Skipped = 0x00, - Oversamplingx1 = 0x01, - Oversamplingx2 = 0x02, - Oversamplingx4 = 0x03, - Oversamplingx8 = 0x04, - Oversamplingx16 = 0x05 - }; - - enum StandbyTimeMS - { - ST_0_5 = 0x00, - ST_62_5 = 0x01, - ST_125 = 0x02, - ST_250 = 0x03, - ST_500 = 0x04, - ST_1000 = 0x05, - ST_10 = 0x06, - ST_20 = 0x07 - }; - - enum FilterCoeff - { - FC_OFF = 0x00, - FC_2 = 0x01, - FC_4 = 0x02, - FC_58 = 0x03, - FC_16 = 0x04 - }; - /// Reads the ID of the device uint8_t read_id(); @@ -80,12 +42,13 @@ namespace smooth::application::sensor /// \param pressure Pressure oversampling /// \param temperature Temperature oversampling /// \return true on success, false on failure - bool configure_sensor(SensorMode mode, - OverSampling humidity, - OverSampling pressure, - OverSampling temperature, - StandbyTimeMS standby, - FilterCoeff filter_coeff); + bool configure_sensor(BME280Core::SensorMode mode, + BME280Core::OverSampling humidity, + BME280Core::OverSampling pressure, + BME280Core::OverSampling temperature, + BME280Core::StandbyTimeMS standby, + BME280Core::FilterCoeff filter_coeff, + BME280Core::SpiInterface spi_interface = BME280Core::SpiInterface::SPI_4_WIRE); /// Reads the current configuration /// \param mode The mode @@ -93,12 +56,13 @@ namespace smooth::application::sensor /// \param pressure Pressure oversampling /// \param temperature Temperature oversampling /// \return true on success, false on failure - bool read_configuration(SensorMode& mode, - OverSampling& humidity, - OverSampling& pressure, - OverSampling& temperature, - StandbyTimeMS& standby, - FilterCoeff& filter); + bool read_configuration(BME280Core::SensorMode& mode, + BME280Core::OverSampling& humidity, + BME280Core::OverSampling& pressure, + BME280Core::OverSampling& temperature, + BME280Core::StandbyTimeMS& standby, + BME280Core::FilterCoeff& filter, + BME280Core::SpiInterface& spi_interface); /// Reads the status of the device /// \param is_measuring true if the device is doing conversions. @@ -114,28 +78,6 @@ namespace smooth::application::sensor bool read_measurements(float& humidity, float& pressure, float& temperature); private: - // These calculation methods are based on those in the datasheet at - // https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BME280_DS001-11.pdf - // as of 2017-08-20. The revision of the code is 1.1 according to that document. - typedef int32_t BME280_S32_t; - typedef uint32_t BME280_U32_t; - typedef int64_t BME280_S64_t; - - BME280_S32_t t_fine; - - // Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC. - // t_fine carries fine temperature as global value - BME280_S32_t BME280_compensate_T_int32(BME280_S32_t adc_T); - - // Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 integer bits and 8 fractional - // bits). - // Output value of “24674867” represents 24674867/256 = 96386.2 Pa = 963.862 hPa - BME280_U32_t BME280_compensate_P_int64(BME280_S32_t adc_P); - - // Returns humidity in %RH as unsigned 32 bit integer in Q22.10 format (22 integer and 10 fractional bits). - // Output value of “47445” represents 47445/1024 = 46.333 %RH - BME280_U32_t BME280_compensate_H_int32(BME280_S32_t adc_H); - template bool read_16bit(uint8_t start_reg, T& target) { @@ -194,41 +136,6 @@ namespace smooth::application::sensor bool read_trimming_parameters(); bool trimming_read = false; - - const uint8_t HUM_LSB_REG = 0xFE; - const uint8_t HUM_MSB_REG = 0xFD; - const uint8_t TEMP_XLSB_REG = 0xFC; - const uint8_t TEMP_LSB_REG = 0xFB; - const uint8_t TEMP_MSB_REG = 0xFA; - const uint8_t PRESS_XLSB_REG = 0xF9; - const uint8_t PRESS_LSB_REG = 0xF8; - const uint8_t PRESS_MSB_REG = 0xF7; - const uint8_t CONFIG_REG = 0xF5; - const uint8_t CTRL_MEAS_REG = 0xF4; - const uint8_t STATUS_REG = 0xF3; - const uint8_t CTRL_HUM_REG = 0xF2; - const uint8_t RESET_REG = 0xE0; - const uint8_t ID_REG = 0xD0; - - const uint8_t DIG_T1_REG = 0x88; - const uint8_t DIG_T2_REG = 0x8A; - const uint8_t DIG_T3_REG = 0x8C; - - const uint8_t DIG_P1_REG = 0x8E; - const uint8_t DIG_P2_REG = 0x90; - const uint8_t DIG_P3_REG = 0x92; - const uint8_t DIG_P4_REG = 0x94; - const uint8_t DIG_P5_REG = 0x96; - const uint8_t DIG_P6_REG = 0x98; - const uint8_t DIG_P7_REG = 0x9A; - const uint8_t DIG_P8_REG = 0x9C; - const uint8_t DIG_P9_REG = 0x9E; - - const uint8_t DIG_H1_REG = 0xA1; - const uint8_t DIG_H2_REG = 0xE1; - const uint8_t DIG_H3_REG = 0xE2; - const uint8_t DIG_H4_REG = 0xE4; // 0xE4 / 0xE5[3:0] - const uint8_t DIG_H5_REG = 0xE5; // 0xE5[7:4] / 0xE6 - const uint8_t DIG_H6_REG = 0xE7; + smooth::application::sensor::BME280Core bme280_core{}; }; } diff --git a/lib/smooth/include/smooth/application/io/spi/BME280Core.h b/lib/smooth/include/smooth/application/io/spi/BME280Core.h new file mode 100644 index 00000000..0f997234 --- /dev/null +++ b/lib/smooth/include/smooth/application/io/spi/BME280Core.h @@ -0,0 +1,231 @@ +/* +Smooth - A C++ framework for embedded programming on top of Espressif's ESP-IDF +Copyright 2019 Per Malmberg (https://gitbub.com/PerMalmberg) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#pragma once + +#include +#include "smooth/core/util/FixedBuffer.h" + +namespace smooth::application::sensor +{ + // https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BME280_DS001-11.pdf + class BME280Core + { + public: + static constexpr uint8_t HUM_LSB_REG = 0xFE; + static constexpr uint8_t HUM_MSB_REG = 0xFD; + static constexpr uint8_t TEMP_XLSB_REG = 0xFC; + static constexpr uint8_t TEMP_LSB_REG = 0xFB; + static constexpr uint8_t TEMP_MSB_REG = 0xFA; + static constexpr uint8_t PRESS_XLSB_REG = 0xF9; + static constexpr uint8_t PRESS_LSB_REG = 0xF8; + static constexpr uint8_t PRESS_MSB_REG = 0xF7; + static constexpr uint8_t CONFIG_REG = 0xF5; + static constexpr uint8_t CTRL_MEAS_REG = 0xF4; + static constexpr uint8_t STATUS_REG = 0xF3; + static constexpr uint8_t CTRL_HUM_REG = 0xF2; + static constexpr uint8_t RESET_REG = 0xE0; + static constexpr uint8_t ID_REG = 0xD0; + static constexpr uint8_t CALIB00_REG = 0x88; + static constexpr uint8_t CALIB25_REG = 0xA1; + static constexpr uint8_t CALIB26_REG = 0xE1; + + enum SensorMode + { + Sleep = 0x00, + Forced = 0x01, + Normal = 0x03 + }; + + static const constexpr char* SensorModeStrings[] = { "Sleep", + "Forced", + "Forced", + "Normal" + }; + + enum OverSampling + { + Skipped = 0x00, + Oversamplingx1 = 0x01, + Oversamplingx2 = 0x02, + Oversamplingx4 = 0x03, + Oversamplingx8 = 0x04, + Oversamplingx16 = 0x05 + }; + + static const constexpr char* OverSamplingStrings[] = { "Skipped", + "Oversamplingx1", + "Oversamplingx2", + "Oversamplingx4", + "Oversamplingx8", + "Oversamplingx16" + }; + + enum StandbyTimeMS + { + ST_0_5 = 0x00, + ST_62_5 = 0x01, + ST_125 = 0x02, + ST_250 = 0x03, + ST_500 = 0x04, + ST_1000 = 0x05, + ST_10 = 0x06, + ST_20 = 0x07 + }; + + static const constexpr char* StandbyTimesStrings[] = { "ST_0_5", + "ST_26_5", + "ST_125", + "ST_250", + "ST_500", + "ST_1000", + "ST_10", + "ST_20" } + ; + + enum FilterCoeff + { + FC_OFF = 0x00, + FC_2 = 0x01, + FC_4 = 0x02, + FC_58 = 0x03, + FC_16 = 0x04 + }; + + static const constexpr char* FilterCoeffStrings[] = { "FC_OFF", + "FC_2", + "FC_4", + "FC_58", + "FC_16" + }; + + enum SpiInterface + { + SPI_4_WIRE = 0x00, + SPI_3_WIRE = 0x01 + }; + + static const constexpr char* SpiInterfaceStrings[] = { "SPI_4_WIRE", + "SPI_3_WIRE" + }; + + BME280Core(); + + /// Get a datagram that contains register addresses and data that will be used to configure the BME280 + /// \param datagram A vector that holds the configuration register addresses and data + /// \param mode The mode setting + /// \param humidity The humidity oversampling setting + /// \param pressure The pressure oversampling setting + /// \param temperature The temperature oversampling setting + /// \param standby The standby time setting + /// \param filter_coeff The filter coefficient setting + /// \param spi_interface The spi interface setting + void get_configure_sensor_datagram(std::vector& datagram, + SensorMode mode, + OverSampling humidity, + OverSampling pressure, + OverSampling temperature, + StandbyTimeMS standby, + FilterCoeff filter_coeff, + SpiInterface spi_interface); + + /// Get the current configuration settings + /// \param ctrl_hum_data Data read from register 0xF2 + /// \param ctrl_meas_data Data read from register 0xF4 + /// \param config_data Data read from register 0xF5 + /// \param mode The mode configuration setting will be written to this address + /// \param humidity The humidity configuration setting will be written to this address + /// \param pressure The pressure configuration setting will be written to this address + /// \param temperature The temperature configuration setting will be written to this address + /// \param standby The standby times configuration setting will be written to this address + /// \param filter_coeff The filter coefficient configuration setting will be written to this address + /// \param spi_interface The spi_interface configuration setting will be written to this address + void get_configuration_settings(uint8_t ctrl_hum_data, + uint8_t ctrl_meas_data, + uint8_t config_data, + SensorMode& mode, + OverSampling& humidity, + OverSampling& pressure, + OverSampling& temperature, + StandbyTimeMS& standby, + FilterCoeff& filter, + SpiInterface& spi_interface); + + /// Get the compensated measurements + /// \param measurment_data A container that holds the data read from measurement registers 0xF7..0xFE + /// \param humidity Compensated Humidity, in %RH + /// \param pressure Compenstated Pressure, in Pa + /// \param temperature Compensated Temperature in degree Celsius + //void get_measurements(std::array& measurement_data, + void get_measurements(core::util::FixedBuffer& measurement_data, + float& humidity, + float& pressure, + float& temperature); + + /// Populate the trimming registers for later use when calculating the compensated temp, press and hum + // values + /// \param calibration_data A container that holds the data read from calibration registers 0x88..0x9F, + // 0xA1, 0xE1..0xE7. + void populate_trimming_registers(core::util::FixedBuffer& calibration_data); + + private: + using BME280_S32_t = int32_t; + using BME280_U32_t = uint32_t; + using BME280_S64_t = int64_t; + + BME280_S32_t t_fine; + + // Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC. + // t_fine carries fine temperature as global value + BME280_S32_t BME280_compensate_T_int32(BME280_S32_t adc_T); + + // Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 integer bits and 8 fractional + // bits). + // Output value of “24674867” represents 24674867/256 = 96386.2 Pa = 963.862 hPa + BME280_U32_t BME280_compensate_P_int64(BME280_S32_t adc_P); + + // Returns humidity in %RH as unsigned 32 bit integer in Q22.10 format (22 integer and 10 fractional bits). + // Output value of “47445” represents 47445/1024 = 46.333 %RH + BME280_U32_t BME280_compensate_H_int32(BME280_S32_t adc_H); + + // Structure that contains the trimming registers. These registers are used in the compensation formulas + struct TrimmingType + { + uint16_t dig_T1; + int16_t dig_T2; + int16_t dig_T3; + + uint16_t dig_P1; + int16_t dig_P2; + int16_t dig_P3; + int16_t dig_P4; + int16_t dig_P5; + int16_t dig_P6; + int16_t dig_P7; + int16_t dig_P8; + int16_t dig_P9; + + uint8_t dig_H1; + int16_t dig_H2; + uint8_t dig_H3; + int16_t dig_H4; // 0xE4 / 0xE5[3:0] | [11:4] / [3:0] + int16_t dig_H5; // 0xE5[7:4] / 0xE6 | [3:0] / [11:4] + int8_t dig_H6; + } + + trimming; + }; +} diff --git a/lib/smooth/include/smooth/application/io/spi/BME280SPI.h b/lib/smooth/include/smooth/application/io/spi/BME280SPI.h new file mode 100644 index 00000000..6bad9d0d --- /dev/null +++ b/lib/smooth/include/smooth/application/io/spi/BME280SPI.h @@ -0,0 +1,143 @@ +/* +Smooth - A C++ framework for embedded programming on top of Espressif's ESP-IDF +Copyright 2019 Per Malmberg (https://gitbub.com/PerMalmberg) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/************************************************************************************ + SPECIAL NOTES SPECIAL NOTES SPECIAL NOTES + + NOTE 1: This driver is written to work 4-wire SPI interface see page 32 of the + datasheet. + + NOTE 2: The BME280 requires special handling when writing to the device. + To write to a particular register the high order bit (b7) of the register address + must be 0. See figures 11 and 12 on page 33 of datasheet. + ************************************************************************************/ +#pragma once + +#include "smooth/core/io/spi/Master.h" +#include "smooth/core/io/spi/SPIDevice.h" +#include "smooth/application/io/spi/BME280Core.h" +#include "smooth/core/io/spi/SpiDmaFixedBuffer.h" + +namespace smooth::application::sensor +{ + class BME280SPI : public core::io::spi::SPIDevice + { + public: + BME280SPI(std::mutex& guard, + gpio_num_t chip_select_pin, + uint8_t spi_command_bits, + uint8_t spi_address_bits, + uint8_t bits_between_address_and_data_phase, + uint8_t spi_mode, + uint8_t spi_positive_duty_cycle, + uint8_t spi_cs_ena_posttrans, + int spi_clock_speed_hz, + uint32_t spi_device_flags, + int spi_queue_size, + bool use_pre_transaction_callback, + bool use_post_transaction_callback); + + /// Initialize + bool init(spi_host_device_t host); + + /// Reads the ID of the device + uint8_t read_id(); + + /// Resets the device + bool reset(); + + /// Configures the sensor + /// \param mode The mode setting + /// \param humidity The humidity oversampling setting + /// \param pressure The pressure oversampling setting + /// \param temperature The temperature oversampling setting + /// \param standby The standby time setting + /// \param filter_coeff The filter coefficient setting + /// \param spi_interface The spi interface setting + /// \return true on success, false on failure + bool configure_sensor(BME280Core::SensorMode mode, + BME280Core::OverSampling humidity, + BME280Core::OverSampling pressure, + BME280Core::OverSampling temperature, + BME280Core::StandbyTimeMS standby, + BME280Core::FilterCoeff filter_coeff, + BME280Core::SpiInterface spi_interface = BME280Core::SpiInterface::SPI_4_WIRE); + + /// Reads the current configuration + /// \param mode The mode configuration setting will be written to this address + /// \param humidity The humidity configuration setting will be written to this address + /// \param pressure The pressure configuration setting will be written to this address + /// \param temperature The temperature configuration setting will be written to this address + /// \param standby The standby times configuration setting will be written to this address + /// \param filter_coeff The filter coefficient configuration setting will be written to this address + /// \param spi_interface The spi_interface configuration setting will be written to this address + /// \return true on success, false on failure + bool read_configuration(BME280Core::SensorMode& mode, + BME280Core::OverSampling& humidity, + BME280Core::OverSampling& pressure, + BME280Core::OverSampling& temperature, + BME280Core::StandbyTimeMS& standby, + BME280Core::FilterCoeff& filter, + BME280Core::SpiInterface& spi_interface); + + /// Reads the status of the device + /// \param is_measuring true if the device is doing conversions. + /// \param updating_from_nvm true if data is being copied from NVM to image registers. + /// \return true if status could be read, otherwise false. + bool read_status(bool& is_measuring, bool& updating_from_nvm); + + /// Reads the current measurements + /// \param humidity Humidity, in %RH + /// \param pressure Pressure, in Pa + /// \param temperature Temperature in degree Celsius + /// \return true on success, false on failure. + bool read_measurements(float& humidity, float& pressure, float& temperature); + + private: + /// Pre Transmission Action + /// The operations that need to be performed before the spi transaction is started + void pre_transmission_action() override + { + } + + /// Post Transmission Action + /// The operations that need to be performed after the spi transaction has ended + void post_transmission_action() override + { + } + + /// Write + /// \param txdata A pointer to the first element of the transmit data + /// \param length The number of bytes in txdata + bool write(const uint8_t* txdata, size_t length); + + /// Read + /// \param bme280_reg The BME280 register we wnat to read + /// \param rxdata A pointer to container that will hold the receive data + /// \param length The number of bytes that will be read + bool read(const uint8_t bme280_reg, uint8_t* rxdata, size_t length); + + /// Read trimming parameters; will be used to calculated compensated measurements + bool read_trimming_parameters(); + + gpio_num_t cs_pin; + bool trimming_read{ false }; + smooth::application::sensor::BME280Core bme280_core{}; + smooth::core::io::spi::SpiDmaFixedBuffer rxdata; + smooth::core::io::spi::SpiDmaFixedBuffer rx_measurement_data; + }; +} diff --git a/lib/smooth/include/smooth/core/io/spi/Master.h b/lib/smooth/include/smooth/core/io/spi/Master.h new file mode 100644 index 00000000..7a1d0ec6 --- /dev/null +++ b/lib/smooth/include/smooth/core/io/spi/Master.h @@ -0,0 +1,102 @@ +/* +Smooth - A C++ framework for embedded programming on top of Espressif's ESP-IDF +Copyright 2019 Per Malmberg (https://gitbub.com/PerMalmberg) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#pragma once + +#include +#include +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#include +#include +#include +#pragma GCC diagnostic pop + +namespace smooth::core::io::spi +{ + enum SPI_DMA_Channel + { + DMA_NONE = 0, + DMA_1, + DMA_2, + }; + + class Master + { + public: + /// Create a driver for the specified host using the specified I/O pins. + /// @note Set unused pins to -1. + /// \param host The host, either HSPI_HOST or HSPI_HOST. + /// \param mosi Master Output, Slave Input, i.e. data pin for sending data to slave. + /// \param miso Master Input, Slave Output, i.e. data pin for receiving data from slave. + /// \param clock Clock pin. + /// \param transfer_size Maximum transfer size, in bytes. Defaults to 4094 if 0. + /// \param quadwp_io_num GPIO pin for WP (Write Protect) signal used in D2 4-bit communication modes + /// \param quadhd_io_num GPIO pin for HD (HolD) used in D3 4-bit communication modes + Master(spi_host_device_t host, + SPI_DMA_Channel dma_channel, + gpio_num_t mosi, + gpio_num_t miso, + gpio_num_t clock, + int transfer_size = 0, + gpio_num_t quadwp_io_num = GPIO_NUM_NC, + gpio_num_t quadhd_io_num = GPIO_NUM_NC + ); + + ~Master(); + + /// Initialize the SPI host. + /// \return true on success, false on failure + bool initialize(); + + /// Create a device of the given type. + /// \tparam DeviceType The type of device to create must inherit from SPIDevice + /// \tparam Args Parameter argument types + /// \param args Additional arguments that should be passed to the device. + /// \return A unique pointer to a device, or an empty on failure. + template + std::unique_ptr create_device(Args&& ... args); + + private: + /// Do initailization + /// Performs steps to initialize the SPI Master + void do_initialization(); + + /// Deinitialize + /// Perfoms steps to de-initialize the SPI Master + void deinitialize(); + + bool initialized = false; + std::mutex guard{}; + spi_bus_config_t bus_config{}; + spi_host_device_t host; + SPI_DMA_Channel dma_channel; + }; + + template + std::unique_ptr Master::create_device(Args&& ... args) + { + std::unique_ptr dev; + + if (initialize()) + { + std::lock_guard lock(guard); + dev = std::make_unique(guard, std::forward(args)...); + } + + return dev; + } +} diff --git a/lib/smooth/include/smooth/core/io/spi/SPIDevice.h b/lib/smooth/include/smooth/core/io/spi/SPIDevice.h new file mode 100644 index 00000000..356d33fa --- /dev/null +++ b/lib/smooth/include/smooth/core/io/spi/SPIDevice.h @@ -0,0 +1,141 @@ +/* +Smooth - A C++ framework for embedded programming on top of Espressif's ESP-IDF +Copyright 2019 Per Malmberg (https://gitbub.com/PerMalmberg) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#pragma once + +#include +#include +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#include +#include +#pragma GCC diagnostic pop + +namespace smooth::core::io::spi +{ + /// Base class for all SPI devices + class SPIDevice + { + public: + /// Base constructor for SPI devices. + /// @note These arguments corresponds to those in the spi_device_interface_config_t structure. + /// \param guard The guard mutex (owned by the SPI Master) used to synchronize SPI transmisions between + // devices. + /// \param spi_command_bits Number of bits in command phase (0-16) + /// \param spi_address_bits Number of bits in address phase (0-64) + /// \param bits_between_address_and_data_phase Number of bits to insert between address and data phase + /// \param spi_mode SPI mode (0-3) + /// \param spi_positive_duty_cycle Duty cycle of positive clock, in 1/256th increments (128 = 50%/50% duty). + // Setting this to 0 (=not setting it) is equivalent to setting this to 128. + /// \param spi_cs_ena_posttrans Amount of SPI bit-cycles the cs should stay active after the transmission + // (0-16) + /// \param spi_clock_speed_hz Clock speed, in Hz + /// \param spi_device_flags Bitwise OR of SPI_DEVICE_* flags + /// \param spi_queue_size Transaction queue size. This sets how many transactions can be 'in the air' + // (queued using spi_device_queue_trans but not yet finished using spi_device_get_trans_result) at the same + // time + /// \param use_pre_transaction_callback If true the pre_transaction_callback will be used. + /// \param use_post_transaction_callback If true the post_transaction_callback will be used. + SPIDevice(std::mutex& guard, + uint8_t spi_command_bits, + uint8_t spi_address_bits, + uint8_t bits_between_address_and_data_phase, + uint8_t spi_mode, + uint8_t spi_positive_duty_cycle, + uint8_t spi_cs_ena_posttrans, + int spi_clock_speed_hz, + uint32_t spi_device_flags, + int spi_queue_size, + bool use_pre_transaction_callback, + bool use_post_transaction_callback); + + virtual ~SPIDevice(); + + /// get guard + /// \return Returns the mutex + [[nodiscard]] std::mutex& get_guard() const + { + return guard; + } + + protected: + /// Initialize the SPI Device + /// \param host The host being used for the SPI Device, VSPI or HSPI + /// \param chip_select The chip select pin or -1 if not used + /// \return Returns true on success, false on failure + bool initialize(spi_host_device_t host, gpio_num_t chip_select); + + /// Write + /// \param transaction The transaction to write to the SPI device + /// \return Returns true on success, false on failure + bool write(spi_transaction_t& transaction); + + /// Polling write + /// \param transaction The transaction to write to SPI Device. Will return + /// when write has be completed. + /// \return Returns true on success, false on failure + bool polling_write(spi_transaction_t& transaction); + + /// Queue transactions + /// \param transaction The transaction to be place in the queue + /// \return Returns true on success, false on failure + bool queue_transaction(spi_transaction_t& transaction); + + /// Wait for transaction to finish + /// Waits for the queued transaction to finish + /// \return Returns true on success, false on failure + bool wait_for_transaction_to_finish(); + + /// Override this method to perform pre-transmission actions. + /// @warning This method is runs in ISR context + virtual void pre_transmission_action() + { + } + + /// Override this method to perform post-transmission actions. + /// @warning This method is runs in ISR context + virtual void post_transmission_action() + { + } + + private: + /// Pre Transmission Callback + /// C style callback required by ESP32 SPI driver + /// \param transaction The current transaction + static void pre_transmission_callback(spi_transaction_t* trans); + + /// Post Transmission Callback + /// C style callback required by ESP32 SPI driver + /// \param transaction The current transaction + static void post_transmission_callback(spi_transaction_t* trans); + + spi_device_interface_config_t config{}; + spi_device_handle_t dev = nullptr; + std::mutex& guard; + + /// Convert time to ticks + /// \param ms Time + /// \return Ticks + [[nodiscard]] inline TickType_t to_tick(std::chrono::milliseconds ms) const + { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" + + return pdMS_TO_TICKS(ms.count()); +#pragma GCC diagnostic pop + } + }; +} diff --git a/lib/smooth/include/smooth/core/io/spi/SpiDmaFixedBuffer.h b/lib/smooth/include/smooth/core/io/spi/SpiDmaFixedBuffer.h new file mode 100644 index 00000000..a5dbce3c --- /dev/null +++ b/lib/smooth/include/smooth/core/io/spi/SpiDmaFixedBuffer.h @@ -0,0 +1,59 @@ +/* +Smooth - A C++ framework for embedded programming on top of Espressif's ESP-IDF +Copyright 2019 Per Malmberg (https://gitbub.com/PerMalmberg) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#pragma once + +#include "smooth/core/util/DmaFixedBuffer.h" +#include "smooth/core/logging/log.h" + +using namespace smooth::core::logging; + +namespace smooth::core::io::spi +{ + template + class SpiDmaFixedBuffer : public smooth::core::util::DmaFixedBuffer + { + public: + static constexpr const char* TAG = "SpiDmaFixedBuffer"; + + // Constructor derived class of DmaFixedBuffer + // Note: Since this buffer is created by DmaFixedBuffer and it is using heap_caps_malloc we do not need + // to check the buffer address for being on a 32 bit boundary. The C standard guarantees the result of + // heap_caps_malloc() is a 32-bit aligned pointer. + SpiDmaFixedBuffer() : smooth::core::util::DmaFixedBuffer(MALLOC_CAP_DMA) + { + // The SPI Master requires the DMA data to be length of multiples of 32 bits to avoid heap corruption. + static_assert(Size % 4 == 0, "spi dma buffer must have a length of multiple of 32 bits, i.e. 4 bytes"); + } + + // Destructor + ~SpiDmaFixedBuffer() = default; + + /// Checks to see if buffer has been allocated + /// \return Returns true if buffer is allocated; false if buffer is not allocted (no space on heap) + bool is_buffer_allocated() + { + bool res = this->data() != nullptr; + + if (!res) + { + Log::error(TAG, "spi dma buffer has NOT been allocated"); + } + + return res; + } + }; +} diff --git a/lib/smooth/include/smooth/core/util/DmaFixedBuffer.h b/lib/smooth/include/smooth/core/util/DmaFixedBuffer.h new file mode 100644 index 00000000..846e5a79 --- /dev/null +++ b/lib/smooth/include/smooth/core/util/DmaFixedBuffer.h @@ -0,0 +1,67 @@ +/* +Smooth - A C++ framework for embedded programming on top of Espressif's ESP-IDF +Copyright 2019 Per Malmberg (https://gitbub.com/PerMalmberg) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#pragma once + +#include +#include +#include "smooth/core/logging/log.h" + +namespace smooth::core::util +{ + template + class DmaFixedBuffer + { + public: + constexpr size_t size() + { + return sizeof(T) * Size; + } + + // Constructor + DmaFixedBuffer(const uint32_t malloc_cap_type) + { + buff = static_cast(heap_caps_malloc(Size, malloc_cap_type)); + } + + // Destructor + virtual ~DmaFixedBuffer() + { + heap_caps_free(buff); + } + + /// \return Returns pointer to first element in buffer + T* data() + { + return buff; + } + + const T& operator[](size_t index) const + { + // Prevent going outside buffer + return buff[std::max(0u, std::min(size() - 1, index))]; + } + + T& operator[](size_t index) + { + // Prevent going outside buffer + return buff[std::max(static_cast(0), std::min(size() - 1, index))]; + } + + protected: + T* buff; + }; +} diff --git a/mock-idf/components/driver/include/driver/spi_common.h b/mock-idf/components/driver/include/driver/spi_common.h new file mode 100644 index 00000000..e7a0f58e --- /dev/null +++ b/mock-idf/components/driver/include/driver/spi_common.h @@ -0,0 +1,86 @@ +// Copyright 2010-2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include +#include "esp_err.h" +#include "hal/spi_types.h" + +/** + * @brief This is a configuration structure for a SPI bus. + * + * You can use this structure to specify the GPIO pins of the bus. Normally, the driver will use the + * GPIO matrix to route the signals. An exception is made when all signals either can be routed through + * the IO_MUX or are -1. In that case, the IO_MUX is used, allowing for >40MHz speeds. + * + * @note Be advised that the slave driver does not use the quadwp/quadhd lines and fields in spi_bus_config_t refering to these lines will be ignored and can thus safely be left uninitialized. + */ +typedef struct { + int mosi_io_num; ///< GPIO pin for Master Out Slave In (=spi_d) signal, or -1 if not used. + int miso_io_num; ///< GPIO pin for Master In Slave Out (=spi_q) signal, or -1 if not used. + int sclk_io_num; ///< GPIO pin for Spi CLocK signal, or -1 if not used. + int quadwp_io_num; ///< GPIO pin for WP (Write Protect) signal which is used as D2 in 4-bit + // communication modes, or -1 if not used. + int quadhd_io_num; ///< GPIO pin for HD (HolD) signal which is used as D3 in 4-bit communication modes, + // or -1 if not used. + int max_transfer_sz; ///< Maximum transfer size, in bytes. Defaults to 4094 if 0. + uint32_t flags; ///< Abilities of bus to be checked by the driver. Or-ed value of + // ``SPICOMMON_BUSFLAG_*`` flags. + int intr_flags; /**< Interrupt flag for the bus to set the priority, and IRAM attribute, see + * ``esp_intr_alloc.h``. Note that the EDGE, INTRDISABLED attribute are ignored + * by the driver. Note that if ESP_INTR_FLAG_IRAM is set, ALL the callbacks of + * the driver, and their callee functions, should be put in the IRAM. + */ +} spi_bus_config_t; + +/** + * @brief Initialize a SPI bus + * + * @warning For now, only supports HSPI and VSPI. + * + * @param host SPI peripheral that controls this bus + * @param bus_config Pointer to a spi_bus_config_t struct specifying how the host should be initialized + * @param dma_chan Either channel 1 or 2, or 0 in the case when no DMA is required. Selecting a DMA channel + * for a SPI bus allows transfers on the bus to have sizes only limited by the amount of + * internal memory. Selecting no DMA channel (by passing the value 0) limits the amount of + * bytes transfered to a maximum of 64. Set to 0 if only the SPI flash uses + * this bus. + * + * @warning If a DMA channel is selected, any transmit and receive buffer used should be allocated in + * DMA-capable memory. + * + * @warning The ISR of SPI is always executed on the core which calls this + * function. Never starve the ISR on this core or the SPI transactions will not + * be handled. + * + * @return + * - ESP_ERR_INVALID_ARG if configuration is invalid + * - ESP_ERR_INVALID_STATE if host already is in use + * - ESP_ERR_NO_MEM if out of memory + * - ESP_OK on success + */ +esp_err_t spi_bus_initialize(spi_host_device_t host, const spi_bus_config_t* bus_config, int dma_chan); + +/** + * @brief Free a SPI bus + * + * @warning In order for this to succeed, all devices have to be removed first. + * + * @param host SPI peripheral to free + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_ERR_INVALID_STATE if not all devices on the bus are freed + * - ESP_OK on success + */ +esp_err_t spi_bus_free(spi_host_device_t host); diff --git a/mock-idf/components/driver/include/driver/spi_master.h b/mock-idf/components/driver/include/driver/spi_master.h new file mode 100644 index 00000000..add9843c --- /dev/null +++ b/mock-idf/components/driver/include/driver/spi_master.h @@ -0,0 +1,281 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include +#include +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/portmacro.h" +#include "soc/soc.h" + +//for spi_bus_initialization funcions. to be back-compatible +#include "driver/spi_common.h" + +/** SPI master clock is divided by 80MHz apb clock. Below defines are example frequencies, and are accurate. Be free to specify a random frequency, it will be rounded to closest frequency (to macros below if above 8MHz). + * 8MHz + */ +#define SPI_MASTER_FREQ_8M (APB_CLK_FREQ/10) +#define SPI_MASTER_FREQ_9M (APB_CLK_FREQ/9) ///< 8.89MHz +#define SPI_MASTER_FREQ_10M (APB_CLK_FREQ/8) ///< 10MHz +#define SPI_MASTER_FREQ_11M (APB_CLK_FREQ/7) ///< 11.43MHz +#define SPI_MASTER_FREQ_13M (APB_CLK_FREQ/6) ///< 13.33MHz +#define SPI_MASTER_FREQ_16M (APB_CLK_FREQ/5) ///< 16MHz +#define SPI_MASTER_FREQ_20M (APB_CLK_FREQ/4) ///< 20MHz +#define SPI_MASTER_FREQ_26M (APB_CLK_FREQ/3) ///< 26.67MHz +#define SPI_MASTER_FREQ_40M (APB_CLK_FREQ/2) ///< 40MHz +#define SPI_MASTER_FREQ_80M (APB_CLK_FREQ/1) ///< 80MHz + +#define SPI_DEVICE_TXBIT_LSBFIRST (1<<0) ///< Transmit command/address/data LSB first instead of the default + // MSB first +#define SPI_DEVICE_RXBIT_LSBFIRST (1<<1) ///< Receive data LSB first instead of the default MSB first +#define SPI_DEVICE_BIT_LSBFIRST (SPI_DEVICE_TXBIT_LSBFIRST|SPI_DEVICE_RXBIT_LSBFIRST) ///< Transmit and + // receive LSB first +#define SPI_DEVICE_3WIRE (1<<2) ///< Use MOSI (=spid) for both sending and receiving data +#define SPI_DEVICE_POSITIVE_CS (1<<3) ///< Make CS positive during a transaction instead of negative +#define SPI_DEVICE_HALFDUPLEX (1<<4) ///< Transmit data before receiving it, instead of simultaneously +#define SPI_DEVICE_CLK_AS_CS (1<<5) ///< Output clock on CS line if CS is active + +/** There are timing issue when reading at high frequency (the frequency is related to whether iomux pins are used, valid time after slave sees the clock). + * - In half-duplex mode, the driver automatically inserts dummy bits before reading phase to fix the timing issue. Set this flag to disable this feature. + * - In full-duplex mode, however, the hardware cannot use dummy bits, so there is no way to prevent data being read from getting corrupted. + * Set this flag to confirm that you're going to work with output only, or read without dummy bits at your own risk. + */ +#define SPI_DEVICE_NO_DUMMY (1<<6) + +typedef struct spi_transaction_t spi_transaction_t; +typedef void (* transaction_cb_t)(spi_transaction_t* trans); + +/** + * @brief This is a configuration for a SPI slave device that is connected to one of the SPI buses. + */ +typedef struct { + uint8_t command_bits; ///< Default amount of bits in command phase (0-16), used when + // ``SPI_TRANS_VARIABLE_CMD`` is not used, otherwise ignored. + uint8_t address_bits; ///< Default amount of bits in address phase (0-64), used when + // ``SPI_TRANS_VARIABLE_ADDR`` is not used, otherwise ignored. + uint8_t dummy_bits; ///< Amount of dummy bits to insert between address and data phase + uint8_t mode; ///< SPI mode (0-3) + uint8_t duty_cycle_pos; ///< Duty cycle of positive clock, in 1/256th increments (128 = 50%/50% duty). + // Setting this to 0 (=not setting it) is equivalent to setting this to 128. + uint8_t cs_ena_pretrans; ///< Amount of SPI bit-cycles the cs should be activated before the transmission + // (0-16). This only works on half-duplex transactions. + uint8_t cs_ena_posttrans; ///< Amount of SPI bit-cycles the cs should stay active after the transmission + // (0-16) + int clock_speed_hz; ///< Clock speed, divisors of 80MHz, in Hz. See ``SPI_MASTER_FREQ_*``. + int input_delay_ns; /**< Maximum data valid time of slave. The time required between SCLK and MISO + valid, including the possible clock delay from slave to master. The driver uses this value to give an extra + delay before the MISO is ready on the line. Leave at 0 unless you know you need a delay. For better timing + performance at high frequency (over 8MHz), it's suggest to have the right value. + */ + int spics_io_num; ///< CS GPIO pin for this device, or -1 if not used + uint32_t flags; ///< Bitwise OR of SPI_DEVICE_* flags + int queue_size; ///< Transaction queue size. This sets how many transactions can be 'in the air' + // (queued using spi_device_queue_trans but not yet finished using + // spi_device_get_trans_result) at the same time + transaction_cb_t pre_cb; /**< Callback to be called before a transmission is started. + * + * This callback is called within interrupt + * context should be in IRAM for best + * performance, see "Transferring Speed" + * section in the SPI Master documentation for + * full details. If not, the callback may crash + * during flash operation when the driver is + * initialized with ESP_INTR_FLAG_IRAM. + */ + transaction_cb_t post_cb; /**< Callback to be called after a transmission has completed. + * + * This callback is called within interrupt + * context should be in IRAM for best + * performance, see "Transferring Speed" + * section in the SPI Master documentation for + * full details. If not, the callback may crash + * during flash operation when the driver is + * initialized with ESP_INTR_FLAG_IRAM. + */ +} spi_device_interface_config_t; + +#define SPI_TRANS_MODE_DIO (1<<0) ///< Transmit/receive data in 2-bit mode +#define SPI_TRANS_MODE_QIO (1<<1) ///< Transmit/receive data in 4-bit mode +#define SPI_TRANS_USE_RXDATA (1<<2) ///< Receive into rx_data member of spi_transaction_t instead into memory + // at rx_buffer. +#define SPI_TRANS_USE_TXDATA (1<<3) ///< Transmit tx_data member of spi_transaction_t instead of data at + // tx_buffer. Do not set tx_buffer when using this. +#define SPI_TRANS_MODE_DIOQIO_ADDR (1<<4) ///< Also transmit address in mode selected by SPI_MODE_DIO/SPI_MODE_QIO +#define SPI_TRANS_VARIABLE_CMD (1<<5) ///< Use the ``command_bits`` in ``spi_transaction_ext_t`` rather than + // default value in ``spi_device_interface_config_t``. +#define SPI_TRANS_VARIABLE_ADDR (1<<6) ///< Use the ``address_bits`` in ``spi_transaction_ext_t`` rather than + // default value in ``spi_device_interface_config_t``. +#define SPI_TRANS_VARIABLE_DUMMY (1<<7) ///< Use the ``dummy_bits`` in ``spi_transaction_ext_t`` rather than + // default value in ``spi_device_interface_config_t``. + +/** + * This structure describes one SPI transaction. The descriptor should not be modified until the transaction finishes. + */ +struct spi_transaction_t { + uint32_t flags; ///< Bitwise OR of SPI_TRANS_* flags + uint16_t cmd; /**< Command data, of which the length is set in the ``command_bits`` of spi_device_interface_config_t. + * + * NOTE: this field, used to be "command" in ESP-IDF 2.1 and before, is re-written to be used in a new way in ESP-IDF 3.0. + * + * Example: write 0x0123 and command_bits=12 to send command 0x12, 0x3_ (in previous version, you may have to write 0x3_12). + */ + uint64_t addr; /**< Address data, of which the length is set in the ``address_bits`` of spi_device_interface_config_t. + * + * NOTE: this field, used to be "address" in ESP-IDF 2.1 and before, is re-written to be used in a new way in ESP-IDF3.0. + * + * Example: write 0x123400 and address_bits=24 to send address of 0x12, 0x34, 0x00 (in previous version, you may have to write 0x12340000). + */ + size_t length; ///< Total data length, in bits + size_t rxlength; ///< Total data length received, should be not greater than ``length`` in + // full-duplex mode (0 defaults this to the value of ``length``). + void* user; ///< User-defined variable. Can be used to store eg transaction ID. + union { + const void* tx_buffer; ///< Pointer to transmit buffer, or NULL for no MOSI phase + uint8_t tx_data[4]; ///< If SPI_TRANS_USE_TXDATA is set, data set here is sent directly from this + // variable. + }; + + union { + void* rx_buffer; ///< Pointer to receive buffer, or NULL for no MISO phase. Written by 4 bytes-unit + // if DMA is used. + uint8_t rx_data[4]; ///< If SPI_TRANS_USE_RXDATA is set, data is received directly to this variable + }; +}; //the rx data should start from a 32-bit aligned address to get around dma issue. + +/** + * This struct is for SPI transactions which may change their address and command length. + * Please do set the flags in base to ``SPI_TRANS_VARIABLE_CMD_ADR`` to use the bit length here. + */ +typedef struct { + struct spi_transaction_t base; ///< Transaction data, so that pointer to spi_transaction_t can be converted into + // spi_transaction_ext_t + uint8_t command_bits; ///< The command length in this transaction, in bits. + uint8_t address_bits; ///< The address length in this transaction, in bits. + uint8_t dummy_bits; ///< The dummy length in this transaction, in bits. +} spi_transaction_ext_t; + +typedef struct spi_device_t* spi_device_handle_t; ///< Handle for a device on a SPI bus + +/** + * @brief Allocate a device on a SPI bus + * + * This initializes the internal structures for a device, plus allocates a CS pin on the indicated SPI master + * peripheral and routes it to the indicated GPIO. All SPI master devices have three CS pins and can thus control + * up to three devices. + * + * @note While in general, speeds up to 80MHz on the dedicated SPI pins and 40MHz on GPIO-matrix-routed pins are + * supported, full-duplex transfers routed over the GPIO matrix only support speeds up to 26MHz. + * + * @param host SPI peripheral to allocate device on + * @param dev_config SPI interface protocol config for the device + * @param handle Pointer to variable to hold the device handle + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_ERR_NOT_FOUND if host doesn't have any free CS slots + * - ESP_ERR_NO_MEM if out of memory + * - ESP_OK on success + */ +esp_err_t spi_bus_add_device(spi_host_device_t host, + const spi_device_interface_config_t* dev_config, + spi_device_handle_t* handle); + +/** + * @brief Remove a device from the SPI bus + * + * @param handle Device handle to free + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_ERR_INVALID_STATE if device already is freed + * - ESP_OK on success + */ +esp_err_t spi_bus_remove_device(spi_device_handle_t handle); + +/** + * @brief Queue a SPI transaction for interrupt transaction execution. Get the result by ``spi_device_get_trans_result``. + * + * @note Normally a device cannot start (queue) polling and interrupt + * transactions simultaneously. + * + * @param handle Device handle obtained using spi_host_add_dev + * @param trans_desc Description of transaction to execute + * @param ticks_to_wait Ticks to wait until there's room in the queue; use portMAX_DELAY to + * never time out. + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_ERR_TIMEOUT if there was no room in the queue before ticks_to_wait expired + * - ESP_ERR_NO_MEM if allocating DMA-capable temporary buffer failed + * - ESP_ERR_INVALID_STATE if previous transactions are not finished + * - ESP_OK on success + */ +esp_err_t spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t* trans_desc, TickType_t ticks_to_wait); + +/** + * @brief Get the result of a SPI transaction queued earlier by ``spi_device_queue_trans``. + * + * This routine will wait until a transaction to the given device + * succesfully completed. It will then return the description of the + * completed transaction so software can inspect the result and e.g. free the memory or + * re-use the buffers. + * + * @param handle Device handle obtained using spi_host_add_dev + * @param trans_desc Pointer to variable able to contain a pointer to the description of the transaction + that is executed. The descriptor should not be modified until the descriptor is returned by + spi_device_get_trans_result. + * @param ticks_to_wait Ticks to wait until there's a returned item; use portMAX_DELAY to never time + out. + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_ERR_TIMEOUT if there was no completed transaction before ticks_to_wait expired + * - ESP_OK on success + */ +esp_err_t spi_device_get_trans_result(spi_device_handle_t handle, + spi_transaction_t** trans_desc, + TickType_t ticks_to_wait); + +/** + * @brief Send a SPI transaction, wait for it to complete, and return the result + * + * This function is the equivalent of calling spi_device_queue_trans() followed by spi_device_get_trans_result(). + * Do not use this when there is still a transaction separately queued (started) from spi_device_queue_trans() or polling_start/transmit that hasn't been finalized. + * + * @note This function is not thread safe when multiple tasks access the same SPI device. + * Normally a device cannot start (queue) polling and interrupt + * transactions simutanuously. + * + * @param handle Device handle obtained using spi_host_add_dev + * @param trans_desc Description of transaction to execute + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_OK on success + */ +esp_err_t spi_device_transmit(spi_device_handle_t handle, spi_transaction_t* trans_desc); + +/** + * @brief Send a polling transaction, wait for it to complete, and return the result + * + * This function is the equivalent of calling spi_device_polling_start() followed by spi_device_polling_end(). + * Do not use this when there is still a transaction that hasn't been finalized. + * + * @note This function is not thread safe when multiple tasks access the same SPI device. + * Normally a device cannot start (queue) polling and interrupt + * transactions simutanuously. + * + * @param handle Device handle obtained using spi_host_add_dev + * @param trans_desc Description of transaction to execute + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_OK on success + */ +esp_err_t spi_device_polling_transmit(spi_device_handle_t handle, spi_transaction_t* trans_desc); diff --git a/mock-idf/components/driver/spi_master.cpp b/mock-idf/components/driver/spi_master.cpp new file mode 100644 index 00000000..1aef9d47 --- /dev/null +++ b/mock-idf/components/driver/spi_master.cpp @@ -0,0 +1,45 @@ +#include "driver/spi_master.h" + +esp_err_t spi_bus_initialize(spi_host_device_t host, const spi_bus_config_t* bus_config, int dma_chan) +{ + return ESP_OK; +} + +esp_err_t spi_bus_free(spi_host_device_t host) +{ + return ESP_OK; +} + +esp_err_t spi_bus_add_device(spi_host_device_t host, + const spi_device_interface_config_t* dev_config, + spi_device_handle_t* handle) +{ + return ESP_OK; +} + +esp_err_t spi_bus_remove_device(spi_device_handle_t handle) +{ + return ESP_OK; +} + +esp_err_t spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t* trans_desc, TickType_t ticks_to_wait) +{ + return ESP_OK; +} + +esp_err_t spi_device_get_trans_result(spi_device_handle_t handle, + spi_transaction_t** trans_desc, + TickType_t ticks_to_wait) +{ + return ESP_OK; +} + +esp_err_t spi_device_transmit(spi_device_handle_t handle, spi_transaction_t* trans_desc) +{ + return ESP_OK; +} + +esp_err_t spi_device_polling_transmit(spi_device_handle_t handle, spi_transaction_t* trans_desc) +{ + return ESP_OK; +} diff --git a/mock-idf/components/freertos/include/freertos/FreeRTOS.h b/mock-idf/components/freertos/include/freertos/FreeRTOS.h index 6f70f09b..2ebb6538 100644 --- a/mock-idf/components/freertos/include/freertos/FreeRTOS.h +++ b/mock-idf/components/freertos/include/freertos/FreeRTOS.h @@ -1 +1,4 @@ #pragma once + +/* Basic FreeRTOS definitions. */ +#include "projdefs.h" diff --git a/mock-idf/components/heap/heap_caps.cpp b/mock-idf/components/heap/heap_caps.cpp new file mode 100644 index 00000000..a866ac49 --- /dev/null +++ b/mock-idf/components/heap/heap_caps.cpp @@ -0,0 +1,26 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "esp_heap_caps.h" + + +void *heap_caps_malloc( size_t size, uint32_t caps ) +{ + return NULL; +} + +void heap_caps_free( void *ptr) +{ + +} + diff --git a/mock-idf/components/heap/include/esp_heap_caps.h b/mock-idf/components/heap/include/esp_heap_caps.h new file mode 100644 index 00000000..a3b974ed --- /dev/null +++ b/mock-idf/components/heap/include/esp_heap_caps.h @@ -0,0 +1,64 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include +#include + + +/** + * @brief Flags to indicate the capabilities of the various memory systems + */ +#define MALLOC_CAP_EXEC (1<<0) ///< Memory must be able to run executable code +#define MALLOC_CAP_32BIT (1<<1) ///< Memory must allow for aligned 32-bit data accesses +#define MALLOC_CAP_8BIT (1<<2) ///< Memory must allow for 8/16/...-bit data accesses +#define MALLOC_CAP_DMA (1<<3) ///< Memory must be able to accessed by DMA +#define MALLOC_CAP_PID2 (1<<4) ///< Memory must be mapped to PID2 memory space (PIDs are not currently used) +#define MALLOC_CAP_PID3 (1<<5) ///< Memory must be mapped to PID3 memory space (PIDs are not currently used) +#define MALLOC_CAP_PID4 (1<<6) ///< Memory must be mapped to PID4 memory space (PIDs are not currently used) +#define MALLOC_CAP_PID5 (1<<7) ///< Memory must be mapped to PID5 memory space (PIDs are not currently used) +#define MALLOC_CAP_PID6 (1<<8) ///< Memory must be mapped to PID6 memory space (PIDs are not currently used) +#define MALLOC_CAP_PID7 (1<<9) ///< Memory must be mapped to PID7 memory space (PIDs are not currently used) +#define MALLOC_CAP_SPIRAM (1<<10) ///< Memory must be in SPI RAM +#define MALLOC_CAP_INTERNAL (1<<11) ///< Memory must be internal; specifically it should not disappear when flash/spiram cache is switched off +#define MALLOC_CAP_DEFAULT (1<<12) ///< Memory can be returned in a non-capability-specific memory allocation (e.g. malloc(), calloc()) call +#define MALLOC_CAP_INVALID (1<<31) ///< Memory can't be used / list end marker + +/** + * @brief Allocate a chunk of memory which has the given capabilities + * + * Equivalent semantics to libc malloc(), for capability-aware memory. + * + * In IDF, ``malloc(p)`` is equivalent to ``heap_caps_malloc(p, MALLOC_CAP_8BIT)``. + * + * @param size Size, in bytes, of the amount of memory to allocate + * @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type + * of memory to be returned + * + * @return A pointer to the memory allocated on success, NULL on failure + */ +void *heap_caps_malloc(size_t size, uint32_t caps); + + +/** + * @brief Free memory previously allocated via heap_caps_malloc() or heap_caps_realloc(). + * + * Equivalent semantics to libc free(), for capability-aware memory. + * + * In IDF, ``free(p)`` is equivalent to ``heap_caps_free(p)``. + * + * @param ptr Pointer to memory previously returned from heap_caps_malloc() or heap_caps_realloc(). Can be NULL. + */ +void heap_caps_free( void *ptr); + diff --git a/mock-idf/components/soc/include/hal/spi_types.h b/mock-idf/components/soc/include/hal/spi_types.h new file mode 100644 index 00000000..a6967d91 --- /dev/null +++ b/mock-idf/components/soc/include/hal/spi_types.h @@ -0,0 +1,16 @@ +#pragma once + +/** + * @brief Enum with the three SPI peripherals that are software-accessible in it + */ +typedef enum +{ + SPI1_HOST = 0, ///< SPI1 + SPI2_HOST = 1, ///< SPI2 + SPI3_HOST = 2, ///< SPI3 +} spi_host_device_t; + +//alias for different chips +#define SPI_HOST SPI1_HOST +#define HSPI_HOST SPI2_HOST +#define VSPI_HOST SPI3_HOST diff --git a/mock-idf/components/soc/include/soc/soc.h b/mock-idf/components/soc/include/soc/soc.h new file mode 100644 index 00000000..6ed3262b --- /dev/null +++ b/mock-idf/components/soc/include/soc/soc.h @@ -0,0 +1,29 @@ +// Copyright 2010-2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +//Periheral Clock {{ +#define APB_CLK_FREQ_ROM ( 26*1000000 ) +#define CPU_CLK_FREQ_ROM APB_CLK_FREQ_ROM +#define CPU_CLK_FREQ APB_CLK_FREQ +#define APB_CLK_FREQ ( 80*1000000 ) //unit: Hz +#define REF_CLK_FREQ ( 1000000 ) +#define UART_CLK_FREQ APB_CLK_FREQ +#define WDT_CLK_FREQ APB_CLK_FREQ +#define TIMER_CLK_FREQ (80000000>>4) //80MHz divided by 16 +#define SPI_CLK_DIV 4 +#define TICKS_PER_US_ROM 26 // CPU is 80MHz +#define GPIO_MATRIX_DELAY_NS 25 + +//}} diff --git a/sdkconfig b/sdkconfig index 975c1021..ee807ffc 100644 --- a/sdkconfig +++ b/sdkconfig @@ -117,6 +117,7 @@ CONFIG_ADC_DISABLE_DAC=y CONFIG_SPI_MASTER_ISR_IN_IRAM=y # CONFIG_SPI_SLAVE_IN_IRAM is not set CONFIG_SPI_SLAVE_ISR_IN_IRAM=y +# CONFIG_UART_ISR_IN_IRAM is not set # CONFIG_EFUSE_CUSTOM_TABLE is not set # CONFIG_EFUSE_VIRTUAL is not set # CONFIG_EFUSE_CODE_SCHEME_COMPAT_NONE is not set @@ -436,6 +437,8 @@ CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=16384 # CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN is not set # CONFIG_MBEDTLS_DEBUG is not set +# CONFIG_MBEDTLS_ECP_RESTARTABLE is not set +# CONFIG_MBEDTLS_CMAC_C is not set CONFIG_MBEDTLS_HARDWARE_AES=y CONFIG_MBEDTLS_HARDWARE_MPI=y # CONFIG_MBEDTLS_MPI_USE_INTERRUPT is not set diff --git a/test/i2c_bme280_test/CMakeLists.txt b/test/i2c_bme280_test/CMakeLists.txt new file mode 100644 index 00000000..46786d51 --- /dev/null +++ b/test/i2c_bme280_test/CMakeLists.txt @@ -0,0 +1,35 @@ +#[[ +Smooth - A C++ framework for embedded programming on top of Espressif's ESP-IDF +Copyright 2019 Per Malmberg (https://gitbub.com/PerMalmberg) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +]] + + + +get_filename_component(TEST_PROJECT ${CMAKE_CURRENT_SOURCE_DIR} NAME) + +set(TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/generated_test_smooth_${TEST_PROJECT}.cpp) +configure_file(${CMAKE_CURRENT_LIST_DIR}/../test.cpp.in ${TEST_SRC}) +set(TEST_PROJECT_DIR ${CMAKE_CURRENT_LIST_DIR}) + +# As project() isn't scriptable and the entire file is evaluated we work around the limitation by generating +# the actual file used for the respective platform. +if(NOT "${COMPONENT_DIR}" STREQUAL "") + configure_file(${CMAKE_CURRENT_LIST_DIR}/../test_project_template_esp.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/generated_test_esp.cmake @ONLY) + include(${CMAKE_CURRENT_BINARY_DIR}/generated_test_esp.cmake) +else() + configure_file(${CMAKE_CURRENT_LIST_DIR}/../test_project_template_linux.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/generated_test_linux.cmake @ONLY) + include(${CMAKE_CURRENT_BINARY_DIR}/generated_test_linux.cmake) +endif() + diff --git a/test/i2c_bme280_test/i2c_bme280_test.cpp b/test/i2c_bme280_test/i2c_bme280_test.cpp new file mode 100644 index 00000000..0f3e7910 --- /dev/null +++ b/test/i2c_bme280_test/i2c_bme280_test.cpp @@ -0,0 +1,176 @@ +/* +Smooth - A C++ framework for embedded programming on top of Espressif's ESP-IDF +Copyright 2019 Per Malmberg (https://gitbub.com/PerMalmberg) +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/**************************************************************************************** + * Typical results from running program esp32-DevKit-v1 + * + * W (74246) APP:: ============ APP TICK TICK ============= + * I (74247) MemStat: Mem type | 8-bit free | Smallest block | Minimum free | 32-bit free | Smallest block | Minimum free + * I (74253) MemStat: INTERNAL | 216840 | 113804 | 216556 | 273556 | 113804 | 273264 + * I (74265) MemStat: DMA | 216840 | 113804 | 216556 | 216840 | 113804 | 216556 + * I (74276) MemStat: SPIRAM | 0 | 0 | 0 | 0 | 0 | 0 + * I (74287) MemStat: + * I (74290) MemStat: Name | Stack | Min free stack | Max used stack + * I (74299) MemStat: MainTask | 16384 | 12084 | 4300 + * I (74307) MemStat: SocketDispatcher | 20480 | 18388 | 2092 + * I (74341) APP:: ........................................ + * E (74348) APP:: BME280 Temperature (degC) = 22.4 + * E (74353) APP:: BME280 Humidity (%RH) = 37.0 + * E (74358) APP:: BME280 Pressure (hPa) = 933.411 + * E (74364) APP:: Barometric Pressure (in Hg) = 27.5636 + * I (74370) APP:: ........................................ + * E (74376) APP:: Mode configuration = Normal + * E (74381) APP:: Temperature configuration = Oversamplingx1 + * E (74387) APP:: Humidity configuration = Oversamplingx1 + * E (74394) APP:: Pressure configuration = Oversamplingx1 + * E (74400) APP:: Standby configuration = ST_1000 + * E (74406) APP:: Filter configuration = FC_OFF + * E (74412) APP:: Spi Interface configuration = SPI_4_WIRE + * I (74417) APP:: ........................................ + ****************************************************************************************/ +#include +#include "i2c_bme280_test.h" +#include "smooth/core/logging/log.h" +#include "smooth/core/SystemStatistics.h" + +#include + +using namespace smooth::core; +using namespace std::chrono; +using namespace smooth::application::sensor; + +namespace i2c_bme280_test +{ + static const char* TAG = "APP"; + + App::App() : Application(APPLICATION_BASE_PRIO, seconds(3)), + i2c_master(I2C_NUM_0, // I2C Port 0 + GPIO_NUM_22, // SCL pin + false, // SCL internal pullup NOT enabled + GPIO_NUM_21, // SDA pin + false, // SDA internal pullup NOT enabled + 100 * 1000) // clock frequency - 100kHz + { + } + + void App::init() + { + Application::init(); + + bme280_initialized = init_BME280I2C(); + Log::info(TAG, "BME280 intialization --- {}", bme280_initialized ? "Succeeded" : "Failed"); + } + + void App::tick() + { + Log::warning(TAG, "============ APP TICK TICK ============="); + SystemStatistics::instance().dump(); + Log::info(TAG, "........................................" ); + + if (bme280_initialized) + { + print_thp_sensor_measurements(); + print_thp_sensor_configuration(); + + //print_thp_sensor_id(); + } + } + + bool App::init_BME280I2C() + { + bool res = false; + auto device = i2c_master.create_device(0x76); // BME280 i2c address + + Log::info(TAG, "Scanning for BME280"); + + if (device->is_present()) + { + Log::info(TAG, "BME280 reset: {}", device->reset()); + + bool measuring = false; + bool loading_from_nvm = false; + + while (!device->read_status(measuring, loading_from_nvm) || loading_from_nvm) + { + Log::info(TAG, "Waiting for BME280 to complete reset operation... {} {}", measuring, loading_from_nvm); + } + + res = device->configure_sensor(BME280Core::SensorMode::Normal, + BME280Core::OverSampling::Oversamplingx1, + BME280Core::OverSampling::Oversamplingx1, + BME280Core::OverSampling::Oversamplingx1, + BME280Core::StandbyTimeMS::ST_1000, + BME280Core::FilterCoeff::FC_OFF); + + Log::info(name, "Configure BME280: {}", res); + + if (res) + { + Log::info(TAG, "BME280 initialized, ID: {}", device->read_id()); + } + else + { + Log::error(name, "Could not init BME280"); + } + } + else + { + Log::error(TAG, "BME280 not present"); + } + + thp_sensor = std::move(device); + + return res; + } + + void App::print_thp_sensor_measurements() + { + float temperature, humidity, pressure; + thp_sensor->read_measurements(humidity, pressure, temperature); + + Log::error(TAG, "BME280 Temperature (degC) = {}", temperature); + Log::error(TAG, "BME280 Humidity (%RH) = {}", humidity); + Log::error(TAG, "BME280 Pressure (hPa) = {}", pressure / 100); + Log::error(TAG, "Barometric Pressure (in Hg) = {}", pressure / static_cast (3386.389)); + Log::info(TAG, "........................................" ); + } + + void App::print_thp_sensor_configuration() + { + BME280Core::SensorMode mode; + BME280Core::OverSampling humidity; + BME280Core::OverSampling pressure; + BME280Core::OverSampling temperature; + BME280Core::StandbyTimeMS standby; + BME280Core::FilterCoeff filter; + BME280Core::SpiInterface spi_interface; + + thp_sensor->read_configuration(mode, humidity, pressure, temperature, standby, filter, spi_interface); + + Log::error(TAG, "Mode configuration = {}", BME280Core::SensorModeStrings[mode] ); + Log::error(TAG, "Temperature configuration = {}", BME280Core::OverSamplingStrings[temperature] ); + Log::error(TAG, "Humidity configuration = {}", BME280Core::OverSamplingStrings[humidity] ); + Log::error(TAG, "Pressure configuration = {}", BME280Core::OverSamplingStrings[pressure] ); + Log::error(TAG, "Standby configuration = {}", BME280Core::StandbyTimesStrings[standby] ); + Log::error(TAG, "Filter configuration = {}", BME280Core::FilterCoeffStrings[filter] ); + Log::error(TAG, "Spi Interface configuration = {}", BME280Core::SpiInterfaceStrings[spi_interface] ); + Log::info(TAG, "........................................" ); + } + + void App::print_thp_sensor_id() + { + Log::error(TAG, "BME280 ID = {:#04x}", thp_sensor->read_id()); + Log::info(TAG, "........................................" ); + } +} diff --git a/test/i2c_bme280_test/i2c_bme280_test.h b/test/i2c_bme280_test/i2c_bme280_test.h new file mode 100644 index 00000000..456b2e05 --- /dev/null +++ b/test/i2c_bme280_test/i2c_bme280_test.h @@ -0,0 +1,45 @@ +/* +Smooth - A C++ framework for embedded programming on top of Espressif's ESP-IDF +Copyright 2019 Per Malmberg (https://gitbub.com/PerMalmberg) +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#pragma once + +#include "smooth/core/Application.h" +#include "smooth/core/task_priorities.h" +#include "smooth/core/io/i2c/Master.h" +#include "smooth/application/io/i2c/BME280.h" + +namespace i2c_bme280_test +{ + class App : public smooth::core::Application + { + public: + App(); + + void init() override; + + void tick() override; + + void print_thp_sensor_measurements(); + + void print_thp_sensor_configuration(); + + void print_thp_sensor_id(); + + private: + bool init_BME280I2C(); + + smooth::core::io::i2c::Master i2c_master; + std::unique_ptr thp_sensor{}; + bool bme280_initialized{ false }; + }; +} diff --git a/test/spi_4_line_devices_test/CMakeLists.txt b/test/spi_4_line_devices_test/CMakeLists.txt new file mode 100644 index 00000000..46786d51 --- /dev/null +++ b/test/spi_4_line_devices_test/CMakeLists.txt @@ -0,0 +1,35 @@ +#[[ +Smooth - A C++ framework for embedded programming on top of Espressif's ESP-IDF +Copyright 2019 Per Malmberg (https://gitbub.com/PerMalmberg) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +]] + + + +get_filename_component(TEST_PROJECT ${CMAKE_CURRENT_SOURCE_DIR} NAME) + +set(TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/generated_test_smooth_${TEST_PROJECT}.cpp) +configure_file(${CMAKE_CURRENT_LIST_DIR}/../test.cpp.in ${TEST_SRC}) +set(TEST_PROJECT_DIR ${CMAKE_CURRENT_LIST_DIR}) + +# As project() isn't scriptable and the entire file is evaluated we work around the limitation by generating +# the actual file used for the respective platform. +if(NOT "${COMPONENT_DIR}" STREQUAL "") + configure_file(${CMAKE_CURRENT_LIST_DIR}/../test_project_template_esp.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/generated_test_esp.cmake @ONLY) + include(${CMAKE_CURRENT_BINARY_DIR}/generated_test_esp.cmake) +else() + configure_file(${CMAKE_CURRENT_LIST_DIR}/../test_project_template_linux.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/generated_test_linux.cmake @ONLY) + include(${CMAKE_CURRENT_BINARY_DIR}/generated_test_linux.cmake) +endif() + diff --git a/test/spi_4_line_devices_test/spi_4_line_devices_test.cpp b/test/spi_4_line_devices_test/spi_4_line_devices_test.cpp new file mode 100644 index 00000000..107bb29f --- /dev/null +++ b/test/spi_4_line_devices_test/spi_4_line_devices_test.cpp @@ -0,0 +1,267 @@ +/* +Smooth - A C++ framework for embedded programming on top of Espressif's ESP-IDF +Copyright 2019 Per Malmberg (https://gitbub.com/PerMalmberg) +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/**************************************************************************************** + * Typical results from running program on esp32-devkit v1 + * + * W (74246) APP:: ============ APP TICK TICK ============= + * I (74247) MemStat: Mem type | 8-bit free | Smallest block | Minimum free | 32-bit free | Smallest block | Minimum free + * I (74253) MemStat: INTERNAL | 215796 | 113804 | 215448 | 273088 | 113804 | 272732 + * I (74265) MemStat: DMA | 215796 | 113804 | 215448 | 215796 | 113804 | 215448 + * I (74276) MemStat: SPIRAM | 0 | 0 | 0 | 0 | 0 | 0 + * I (74287) MemStat: + * I (74290) MemStat: Name | Stack | Min free stack | Max used stack + * I (74299) MemStat: MainTask | 16384 | 12076 | 4308 + * I (74307) MemStat: SocketDispatcher | 20480 | 18380 | 2100 + * I (74315) APP:: ........................................ + * E (74322) APP:: cmd = 0x09 param [0] = 0xa4 + * E (74326) APP:: cmd = 0x09 param [1] = 0x53 + * E (74331) APP:: cmd = 0x09 param [2] = 0x04 + * E (74336) APP:: cmd = 0x09 param [3] = 0x00 + * I (74341) APP:: ........................................ + * E (74348) APP:: BME280 Temperature (degC) = 22.4 + * E (74353) APP:: BME280 Humidity (%RH) = 37.0 + * E (74358) APP:: BME280 Pressure (hPa) = 933.411 + * E (74364) APP:: Barometric Pressure (in Hg) = 27.5636 + * I (74370) APP:: ........................................ + * E (74376) APP:: Mode configuration = Normal + * E (74381) APP:: Temperature configuration = Oversamplingx1 + * E (74387) APP:: Humidity configuration = Oversamplingx1 + * E (74394) APP:: Pressure configuration = Oversamplingx1 + * E (74400) APP:: Standby configuration = ST_1000 + * E (74406) APP:: Filter configuration = FC_OFF + * E (74412) APP:: Spi Interface configuration = SPI_4_WIRE + * I (74417) APP:: ........................................ + * E (74424) APP:: BME280 ID = 0x60 + * I (74427) APP:: ........................................ + ****************************************************************************************/ +#include +#include "spi_4_line_devices_test.h" +#include "smooth/core/logging/log.h" +#include "smooth/core/SystemStatistics.h" + +using namespace smooth::core; +using namespace std::chrono; +using namespace smooth::core::io::spi; +using namespace smooth::application::display; +using namespace smooth::application::sensor; + +namespace spi_4_line_devices_test +{ + static const char* TAG = "APP"; + static const uint8_t READ_ID_INFO = 0x04; + static const uint8_t READ_DISPLAY_STATUS = 0x09; + static const uint8_t READ_DISPLAY_PWR_MODE = 0x0A; + static const uint8_t READ_DISPLAY_MADCTL = 0x0B; + static const uint8_t ONE_PARAM = 0x01; + static const uint8_t TWO_PARAMS = 0x02; + static const uint8_t THREE_PARAMS = 0x03; + static const uint8_t FOUR_PARAMS = 0x04; + + App::App() : Application(APPLICATION_BASE_PRIO, seconds(3)), + spi_host(VSPI_HOST), // Use VSPI as host + + spi_master( + spi_host, // host VSPI + DMA_1, // use DMA + GPIO_NUM_23, // mosi gpio pin + GPIO_NUM_19, // miso gpio pin (full duplex) + GPIO_NUM_18, // clock gpio pin + 0) // max transfer size default of 4096 + { + } + + void App::init() + { + Application::init(); + + ili9341_initialized = init_ILI9341(); + Log::info(TAG, "ILI9341 intialization --- {}", ili9341_initialized ? "Succeeded" : "Failed"); + + bme280_initialized = init_BME280SPI(); + Log::info(TAG, "BME280 intialization --- {}", bme280_initialized ? "Succeeded" : "Failed"); + } + + void App::tick() + { + Log::warning(TAG, "============ APP TICK TICK ============="); + SystemStatistics::instance().dump(); + Log::info(TAG, "........................................" ); + + if (ili9341_initialized) + { + //print_display_parameters(READ_ID_INFO, THREE_PARAMS); + print_display_parameters(READ_DISPLAY_STATUS, FOUR_PARAMS); + + //print_display_parameters(READ_DISPLAY_PWR_MODE, ONE_PARAM); + //print_display_parameters(READ_DISPLAY_MADCTL, ONE_PARAM); + } + + if (bme280_initialized) + { + print_thp_sensor_measurements(); + print_thp_sensor_configuration(); + print_thp_sensor_id(); + } + } + + bool App::init_ILI9341() + { + auto device = spi_master.create_device( + GPIO_NUM_14, // chip select gpio pin + GPIO_NUM_27, // data command gpio pin + GPIO_NUM_33, // reset gpio pin + GPIO_NUM_32, // backlight gpio pin + 0, // spi command_bits + 0, // spi address_bits, + 0, // bits_between_address_and_data_phase, + 0, // spi_mode = 0, + 128, // spi positive_duty_cycle, + 0, // spi cs_ena_posttrans, + SPI_MASTER_FREQ_16M, // spi-sck = 16MHz + 0, // full duplex (4-wire) + 7, // queue_size, + true, // use pre-trans callback + true); // use post-trans callback + + Log::info(TAG, "Initializing of SPI Device: ILI9341"); + bool res = device->init(spi_host); + + if (res) + { + device->reset_display(); + res &= device->send_init_cmds(); + device->set_back_light(true); + display = std::move(device); + } + else + { + Log::error(TAG, "Initializing of SPI Device: ILI9341 --- FAILED"); + } + + return res; + } + + bool App::init_BME280SPI() + { + bool res = false; + auto device = spi_master.create_device( + GPIO_NUM_13, // chip select gpio pin + 0, // spi command_bits + 0, // spi address_bits, + 0, // bits_between_address_and_data_phase, + 0, // spi_mode = 0, + 128, // spi positive_duty_cycle, + 0, // spi cs_ena_posttrans, + SPI_MASTER_FREQ_10M, // spi-sck = 10MHz + 0, // full duplex (4-wire) + 2, // queue_size, + false, // will not use pre-trans callback + false); // will not use post-trans callback + + Log::info(TAG, "Initializing of SPI Device: BME280SPI"); + + if (device->init(spi_host)) + { + Log::info(TAG, "BME280 reset: {}", device->reset()); + + bool measuring = false; + bool loading_from_nvm = false; + + while (!device->read_status(measuring, loading_from_nvm) || loading_from_nvm) + { + Log::info(TAG, "Waiting for BME280 to complete reset operation... {} {}", measuring, loading_from_nvm); + } + + res = device->configure_sensor(BME280Core::SensorMode::Normal, + BME280Core::OverSampling::Oversamplingx1, + BME280Core::OverSampling::Oversamplingx1, + BME280Core::OverSampling::Oversamplingx1, + BME280Core::StandbyTimeMS::ST_1000, + BME280Core::FilterCoeff::FC_OFF); + + Log::info(name, "Configure BME280: {}", res); + + if (res) + { + Log::info(TAG, "BME280 initialized, ID: {}", device->read_id()); + } + else + { + Log::error(name, "Could not init BME280"); + } + } + else + { + Log::error(TAG, "Initializing of SPI Device: BME280SPI --- FAILED"); + } + + thp_sensor = std::move(device); + + return res; + } + + void App::print_display_parameters(const uint8_t cmd, uint8_t param_count) + { + std::vector rxdata; + display->read_params(cmd, rxdata, param_count); + + for (uint8_t i = 0; i < param_count; i++) + { + Log::error(TAG, "cmd = {:#04x} param [{}] = {:#04x}", cmd, i, rxdata.at(i)); + } + + Log::info(TAG, "........................................" ); + } + + void App::print_thp_sensor_measurements() + { + float temperature, humidity, pressure; + thp_sensor->read_measurements(humidity, pressure, temperature); + + Log::error(TAG, "BME280 Temperature (degC) = {}", temperature); + Log::error(TAG, "BME280 Humidity (%RH) = {}", humidity); + Log::error(TAG, "BME280 Pressure (hPa) = {}", pressure / 100); + Log::error(TAG, "Barometric Pressure (in Hg) = {}", pressure / static_cast (3386.389)); + Log::info(TAG, "........................................" ); + } + + void App::print_thp_sensor_configuration() + { + BME280Core::SensorMode mode; + BME280Core::OverSampling humidity; + BME280Core::OverSampling pressure; + BME280Core::OverSampling temperature; + BME280Core::StandbyTimeMS standby; + BME280Core::FilterCoeff filter; + BME280Core::SpiInterface spi_interface; + + thp_sensor->read_configuration(mode, humidity, pressure, temperature, standby, filter, spi_interface); + + Log::error(TAG, "Mode configuration = {}", BME280Core::SensorModeStrings[mode] ); + Log::error(TAG, "Temperature configuration = {}", BME280Core::OverSamplingStrings[temperature] ); + Log::error(TAG, "Humidity configuration = {}", BME280Core::OverSamplingStrings[humidity] ); + Log::error(TAG, "Pressure configuration = {}", BME280Core::OverSamplingStrings[pressure] ); + Log::error(TAG, "Standby configuration = {}", BME280Core::StandbyTimesStrings[standby] ); + Log::error(TAG, "Filter configuration = {}", BME280Core::FilterCoeffStrings[filter] ); + Log::error(TAG, "Spi Interface configuration = {}", BME280Core::SpiInterfaceStrings[spi_interface] ); + Log::info(TAG, "........................................" ); + } + + void App::print_thp_sensor_id() + { + Log::error(TAG, "BME280 ID = {:#04x}", thp_sensor->read_id()); + Log::info(TAG, "........................................" ); + } +} diff --git a/test/spi_4_line_devices_test/spi_4_line_devices_test.h b/test/spi_4_line_devices_test/spi_4_line_devices_test.h new file mode 100644 index 00000000..858d54e6 --- /dev/null +++ b/test/spi_4_line_devices_test/spi_4_line_devices_test.h @@ -0,0 +1,52 @@ +/* +Smooth - A C++ framework for embedded programming on top of Espressif's ESP-IDF +Copyright 2019 Per Malmberg (https://gitbub.com/PerMalmberg) +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#pragma once + +#include "smooth/core/Application.h" +#include "smooth/core/task_priorities.h" +#include "smooth/application/display/ILI9341.h" +#include "smooth/application/io/spi/BME280SPI.h" + +namespace spi_4_line_devices_test +{ + class App : public smooth::core::Application + { + public: + App(); + + void init() override; + + void tick() override; + + void print_display_parameters(const uint8_t cmd, uint8_t param_count); + + void print_thp_sensor_measurements(); + + void print_thp_sensor_configuration(); + + void print_thp_sensor_id(); + + private: + bool init_ILI9341(); + + bool init_BME280SPI(); + + spi_host_device_t spi_host; + smooth::core::io::spi::Master spi_master; + std::unique_ptr display{}; + std::unique_ptr thp_sensor{}; + bool ili9341_initialized{ false }; + bool bme280_initialized{ false }; + }; +}