Differences

This shows you the differences between two versions of the page.

Link to this comparison view

iothings:proiecte:2021:zephyr [2022/01/28 13:18]
andrei_edward.popa [4.Drivers Usage Examples]
iothings:proiecte:2021:zephyr [2022/01/28 19:20] (current)
andrei_edward.popa [Support for Raspberry Pi Pico in Zephyr RTOS]
Line 3: Line 3:
 All drivers were implemented by Andrei-Edward Popa from ACES Master Program and those are licensed under Apache 2.0 Open Source License. ​ All drivers were implemented by Andrei-Edward Popa from ACES Master Program and those are licensed under Apache 2.0 Open Source License. ​
  
-Project repository: https://​github.com/​andrei-edward-popa/​zephyr/+Project repository: ​[[https://​github.com/​andrei-edward-popa/​zephyr|Andrei-Edward Popa Github Repository]]
  
 Branches: rpi_pico_working_uart,​ rpi_pico_working_i2c Branches: rpi_pico_working_uart,​ rpi_pico_working_i2c
 +
 +Demo: [[https://​www.youtube.com/​watch?​v=IdkH4meemCc|UART and I2C Driver Usage in Zephyr Project for Raspberry Pi Pico Board]]
  
 ==== 1.Project Objective and Description ==== ==== 1.Project Objective and Description ====
Line 14: Line 16:
  
 For me, this was a good start to learning how Zephyr is structured, understand the drivers API and how protocols like UART and I2C work and what registers we can use to make the driver functional. So, my project is about drivers for Raspberry Pi Pico board and how can we implement drivers like UART and I2C in this RTOS called Zephyr. For me, this was a good start to learning how Zephyr is structured, understand the drivers API and how protocols like UART and I2C work and what registers we can use to make the driver functional. So, my project is about drivers for Raspberry Pi Pico board and how can we implement drivers like UART and I2C in this RTOS called Zephyr.
 +
 +I will provide all steps for Zephyr installation and add the support for Raspberry Pi Pico below. All the steps are done in Ubuntu 20.04 LTS Linux Distribution.
 +
 +First we need to install west and all its dependencies.
 +  * sudo apt update
 +  * wget https://​apt.kitware.com/​kitware-archive.sh
 +  * sudo bash kitware-archive.sh
 +  * sudo apt install --no-install-recommends git cmake ninja-build gperf ccache dfu-util device-tree-compiler wget python3-dev python3-pip python3-setuptools python3-tk python3-wheel xz-utils file make gcc gcc-multilib g++-multilib libsdl2-dev
 +  * pip3 install --user -U west
 +  * echo '​export PATH=~/​.local/​bin:"​$PATH"'​ >> ~/.bashrc
 +  * source ~/.bashrc
 +
 +The next step is to initialize the Zephyr Project, update all hal modules and install the Zephyr SDK.
 +  * west init ~/​zephyrproject
 +  * cd ~/​zephyrproject
 +  * west update
 +  * west zephyr-export
 +  * pip3 install --user -r ~/​zephyrproject/​zephyr/​scripts/​requirements.txt
 +  * pip3 install --user -U docutils==0.16
 +  * pip3 install --user -r ~/​zephyrproject/​zephyr/​scripts/​requirements.txt
 +  * cd ~
 +  * wget https://​github.com/​zephyrproject-rtos/​sdk-ng/​releases/​download/​v0.13.1/​zephyr-sdk-0.13.1-linux-x86_64-setup.run
 +  * chmod +x zephyr-sdk-0.13.1-linux-x86_64-setup.run
 +  * ./​zephyr-sdk-0.13.1-linux-x86_64-setup.run -- -d ~/​zephyr-sdk-0.13.1
 +  * sudo cp ~/​zephyr-sdk-0.13.1/​sysroots/​x86_64-pokysdk-linux/​usr/​share/​openocd/​contrib/​60-openocd.rules /​etc/​udev/​rules.d
 +  * sudo udevadm control --reload
 +  * cd ~/​zephyrproject/​modules/​hal
 +  * git clone https://​github.com/​andrei-edward-popa/​hal_raspberrypi_pico raspberrypi
 +  * cd raspberrypi
 +  * git checkout fix_hardware_pwm_i2c_spi
 +
 +Open ~/.bashrc file and add the following environment variables and then execute bash command.
 +  * export ZEPHYR_BASE=/​home/"​user"/​zephyrproject/​zephyr
 +  * export ZEPHYR_GCC_VARIANT=zephyr
 +  * export ZEPHYR_SDK_INSTALL_DIR=/​home/"​user"/​zephyr-sdk-0.13.1
 +  * export ZEPHYR_TOOLCHAIN_VARIANT="​zephyr"​
 +  * export CMAKE_PREFIX_PATH=/​home/"​user"/​zephyrproject/​zephyr/​share/​zephyr-package/​cmake
 +  * export IDF_PATH="/​home/"​user"/​zephyrproject/​modules/​hal/​raspberrypi"​
 +
 +Finally, add my personal remote to your git local repository.
 +  * cd ~/​zephyrproject/​zephyr
 +  * git remote add "​remote_name"​ https://​github.com/​andrei-edward-popa/​zephyr
 +  * git remote update
 +  * git pull
 +  * git checkout rpi_pico_working_uart OR rpi_pico_working_i2c OR rpi_pico_working_i2c_samples
 +
 +All the steps are tested on a fresh machine and it works without problems. You need to change the "​user"​ with your machine username.
 +
 +
  
 ==== 2.Hardware Description ==== ==== 2.Hardware Description ====
Line 72: Line 123:
   * irq_callback_set:​ Set the IRQ callback function pointer.   * irq_callback_set:​ Set the IRQ callback function pointer.
  
-Those are not all API functions for UART driver, but I did not use the async API for this driver. The initialization part and  poll_in and poll_out API functions were implemented by Yonathan. I made only a modification in the init function that disables the FIFOs and that means that the data are not stored in FIFOs anymore, but in a single 8 bit register (like we have a FIFO with one element). The polling method is not enough for using the well known shell powered by Zephyr, so I implemented the interrupt ​driver ​part of the driver. I will describe the functions implemented below:+Those are not all API functions for UART driver, but I did not use the async API for this driver. The initialization part and poll_in and poll_out API functions were implemented by Yonathan. I made only a modification in the init function that disables the FIFOs and that means that the data are not stored in FIFOs anymore, but in a single 8 bit register (like we have a FIFO with one element). The polling method is not enough for using the well known shell module ​powered by Zephyr, so I implemented the interrupt ​driven ​part of the driver. I will describe the functions implemented below:
   * uart_rpi_err_check:​ The data register (DR) contains some bits that are automatically set when errors like overun, frame, parity or break error occurs. I read those bits and I checked what bits are set and return those error bits.   * uart_rpi_err_check:​ The data register (DR) contains some bits that are automatically set when errors like overun, frame, parity or break error occurs. I read those bits and I checked what bits are set and return those error bits.
   * uart_rpi_fifo_fill:​ In order to fill the TX FIFO we need to check if it is full. We can to that by reading the TX FIFO full bit in Flag Register (FR). If the FIFO is not full, we can put the character in data register (DR) and from there it is pushed in TX FIFO.    * uart_rpi_fifo_fill:​ In order to fill the TX FIFO we need to check if it is full. We can to that by reading the TX FIFO full bit in Flag Register (FR). If the FIFO is not full, we can put the character in data register (DR) and from there it is pushed in TX FIFO. 
Line 91: Line 142:
  
 === 3.2 I2C Driver === === 3.2 I2C Driver ===
 +
 +The API that Zephyr makes available to us consists of the following functions:
 +  * configure: Configure operation of a host controller.
 +  * transfer: Perform data transfer to another I2C device in master mode.
 +  * slave_register:​ Registers the provided config as slave device of a controller.
 +  * slave_unregister:​ Unregisters the provided config as slave device.
 +  * recover_bus:​ Recover the I2C bus.
 +
 +I will describe the implementation for RP2040 API functions below:
 +  * i2c_rpi_configure:​ The configuration function needs to register the I2C device in master mode. The first step is to configure the pins used for I2C communication using the pin controller. Before that, we need to configure the baudrate and initialize the device writing some registers. When the slave API is active, we need to configure and enable the interrupts because slave can only works with an interrupt driven implementation. In other case, the slave needs to poll the bus and check if his address is on the bus and this need to be performed periodically and slows down the processor. In master mode, we need to unmask all the interrupts by clearing the Interrupt Mask Reset bit in Interrupt Mask register. After we set the RX and TX FIFOs depth, our configuration is done. This function is called also in the init function of the driver.
 +  * i2c_rpi_transfer:​ The transfer API function takes an i2c_msgs structure as parameter. Object of this type need to be passed when we want to do a transfer. The structure contains a pointer to where to read or write data and the length of the buffer in bytes. The transfer function is generic for read and write, so we need to specify also some flags into the i2c_msgs objects to know when to do a read or a write (I2C_MSG_READ and I2C_MSG_WRITE). We need to get all the messages and if the flag on the current message is set as I2C_MSG_WRITE we need to write to a slave and if the flag is I2C_MSG_READ we need to read from a slave device. The read and write functions for this transfer are implemented separately, but they are quite long to explain, so I'll let you to get to my repository and figured out what's going on there.
 +  * i2c_rpi_slave_register:​ For register the device as a slave, we first need to disable the master mode by clearing the Master Mode bit in Control Register. After that, we need to configure the slave address in Slave Address Register. After we enable all the interrupts from Interrupt Mask Register, the function is done.
 +  * i2c_rpi_slave_unregister:​ The unregister slave function needs to configure the I2C back into master mode. We need to do the reverse operations from slave register and the important part is to disable all the interrupts because master is not implemented with interrupt driven.
 +  * i2c_rpi_recover_bus:​ For recovering the bus, we need to reset and unreset the device by using the Reset Controller from the pico sdk and call the configure function to reconfigure the device in master mode.
 +
 +After the API is done, the master mode works. But now, what about the slave mode? As I say previously on this driver, the slave mode cannot work without interrupts because we don't need to poll the device every time to see if his address is on the bus. So, when we configured the device in slave mode, we need to enable all the interrupts and write some Interrupt Service Routine function to handle that interrupt.
 +
 +Because we want that ISR to be executed faster, I create some non-blocking read and write function that read data from RX FIFO if it is not empty and write data into the TX FIFO if it is not full. We need to read the interrupt status register to see that interrupt occurs and how to treat it. Now let's discuss what happens when a master device what to read or write to a slave:
 +  * When a master device sends data to a slave device, the data is stored into the RX FIFO of the slave. We want to trigger an interrupt if at least one character was written. For that, in slave mode, we need to set the depth of the FIFOs to 1. Now, when the master device sends one character, the RX FIFO of the slave is full and we can check the RX_FULL bit from Interrupt Status Register. If we are in that case, we need to read that data with the non-blocking read function and call a callback function provided by the user (we will discuss about that) of the driver to handle that data that just arrives.
 +  * When a master device wants to read from a slave device, we need to check if the RD_REQ bit (Read Requested) from the Interrupt Status Register is set. If that's the case, we need to clear that interrupt, call a callback function provided by the user to get the value that master needs and write it to the master with the non-blocking write function.
 +  * If the slave detects a stop bit from master, we can call a callback function provided by the user to stop some configuration for read/write from master.
 +
 +The slave callbacks are function provided by the users to instruct the slave device that data to write to a master or what to do with the data that comes from the master device. The API of the slave callbacks is the following:
 +  * write_requested:​ Function called when a write to the device is initiated.
 +  * read_requested:​ Function called when a read from the device is initiated.
 +  * write_received:​ Function called when a write to the device is continued.
 +  * read_processed:​ Function called when a read from the device is continued.
 +  * stop: Function called when a stop condition is observed after a start condition addressed to a particular device.
 +
 +Anytime when we want to use the Raspberry Pi Pico board as an I2C slave, we need to provide those callback functions (maybe not all) for dealing this data read and write from or to a slave.
 +
  
 ==== 4.Drivers Usage Examples ==== ==== 4.Drivers Usage Examples ====
Line 121: Line 203:
 === 4.3 I2C Slave Mode Driver Example === === 4.3 I2C Slave Mode Driver Example ===
  
 +In order to use this application,​ you need to clone my repository from Github and move to rpi_pico_working_i2c branch. After that, you need to do the next commands in terminal:
 +  * west build -b raspberrypi_pico samples/​drivers/​rpi_pico_slave
 +  * cp build/​zephyr/​zephyr.uf2 /​path/​to/​pico/​mount/​point
 +  * minicom -b 115200 /​dev/​ttyUSB0
 +
 +The rpi_pico_slave application was made by me, it is not a default sample in zephyr. For that, you need to checkout to rpi_pico_working_i2c_samples branch and cherry-pick the last commit into the rpi_pico_working_i2c branch. This application configures the I2C0 as a master and the I2C1 as a slave. The master write an addrees and some text to the slave and slave save the text at the specified address into a 256B memory buffer. Then, master reads data from the slave and slave gives the data stored in the last address written by the slave. GP4 and GP14 are the data pins (SDAs) of the I2C IPs and are connected together and GP5 and GP15 are the clock pins SCK pins (SCKs). ​
 +
 +{{ :​iothings:​proiecte:​2021:​index3.jpeg?​300 |}}
 +
 +{{ :​iothings:​proiecte:​2021:​screenshot_from_2022-01-28_13-29-06.png?​900 |}}
  
 ==== 5.Issues and Solutions ==== ==== 5.Issues and Solutions ====
 +
 +During the implementation of the UART Driver I had one major problem which takes me around 4 days to solve. The problem was that the UART IP that is integrated into the RP2040 microcontroller was kept into the ISR forever because some interrupt pins were masked when the initialization of the UART Driver was done. Because the initialization part wasn't done by me, the bug was hard to find. For that, I needed to clear all the interrupts before I configure the IRQ number of UART and enable the interrupt.
 +
 +During the implementation of the I2C Driver I had the some problem as the UART Driver, some interrupts needed to be unmasked before the I2C interrupts to be enabled. Also, the configuration of the FIFOs was buggy because, for some reason, the depth of the FIFOs cannot be larger that 16. In the documentation,​ it says that the FIFOs can have a depth of 256 entries, but it seems like it doesn'​t.
  
 ==== 6.Conclusions ==== ==== 6.Conclusions ====
 +
 +The purpose of this project was to adding more support to the Raspberry Pi Pico board in Zephyr Project. The important drivers that I made are UART Interrupt Driven Driver and I2C Driver, both master and slave modes. In addition, I have a working version of the PWM driver, but I didn't have time to discuss about it, but you can check the rpi_pico_working_pwm branch on my repository. ​
 +
 +The lessons learned during the development of this project are not few and here are just some of them:
 +  * Device Driver Model of the Zephyr Project.
 +  * UART and I2C protocols in depth.
 +  * Create custom applications and use the drivers APIs for creating the applications.
 +
 +For future work, I want to implement the next list of drivers:
 +  * SPI driver
 +  * Watchdog Timer driver
 +  * Real Time Counter driver
 +  * ADC driver
  
  
iothings/proiecte/2021/zephyr.1643368715.txt.gz · Last modified: 2022/01/28 13:18 by andrei_edward.popa
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0