Since I didn't receive the necessary components for my primary project, which is the EcoMetrix Weather Station, I've decided to create a tutorial on how to use the I2C protocol with the CY8CPROTO-062-4343W board in ModusToolbox. In this tutorial, we will explore how I2C works, learn to create an I2C scanner, and delve into interfacing with the ADXL345 I2C accelerometer.
The CY8CPROTO-062-4343W is a development kit by Cypress Semiconductor for IoT and wireless applications. It features a PSoC 6 microcontroller with integrated Wi-Fi and Bluetooth, making it ideal for IoT prototyping and connectivity projects.
I2C offers a versatile combination of the advantages found in both SPI and UART communication. With I2C, it's possible to link multiple slave devices to a single master (similar to SPI), and you can also have multiple master devices in control of single or multiple slaves. This flexibility proves highly beneficial when you need more than one microcontroller to log data onto a single memory card or display text on a shared LCD.
Just like UART communication, I2C operates with only two wires to facilitate data transmission between devices:
SDA (Serial Data) – This line facilitates the exchange of data between the master and the slave.
SCL (Serial Clock) – This line carries the clock signal necessary for synchronization.
I2C operates as a serial communication protocol, enabling the transfer of data bit by bit along a single wire, which is the SDA line. Similar to SPI, I2C follows a synchronous communication method, ensuring that the output of bits is in sync with the sampling of bits, all regulated by a shared clock signal managed by the master.
In I2C communication, data is organized into messages, which, in turn, are divided into data frames. Each message comprises an address frame containing the binary address of the slave device, along with one or more data frames that carry the actual data being sent. Additionally, the message includes start and stop conditions, read/write bits to indicate the direction of data flow, and ACK (acknowledge) or NACK (not acknowledge) bits positioned between each data frame for effective data transfer and synchronization.
I2C communication involves several key elements and procedures:
**Start Condition:** This occurs when the SDA line transitions from a high voltage level to a low voltage level before the SCL line shifts from high to low.
**Stop Condition:** It takes place when the SDA line changes from a low voltage level to a high voltage level after the SCL line shifts from low to high.
**Address Frame:** This frame consists of a 7 or 10-bit sequence unique to each slave device, which identifies the target slave when the master intends to communicate with it.
**Read/Write Bit:** This single bit, positioned at the end of the address frame, indicates whether the master is sending data to the slave (low voltage level) or requesting data from it (high voltage level).
**ACK/NACK Bit:** Following each frame in a message, an acknowledge (ACK) or not acknowledge (NACK) bit is used. If an address frame or data frame is successfully received, an ACK bit is sent back to the sender by the receiving device.
**Addressing:** Unlike SPI, I2C lacks slave select lines, so it utilizes addressing to inform the slave that data is intended for it. The master sends the address of the specific slave it wishes to communicate with to all connected slaves. Each slave compares the received address with its own. If a match is found, it responds with a low voltage ACK bit. If there is no match, the slave remains idle, and the SDA line stays high.
**Read/Write Bit:** Within the address frame, there is a single bit that conveys whether the master intends to write data to the slave (low voltage level) or read data from it (high voltage level).
**Data Frame:** Upon detecting the ACK bit from the slave, the first data frame is ready for transmission. Each data frame is 8 bits long and is sent with the most significant bit first. An ACK/NACK bit immediately follows each data frame to confirm successful reception before the next data frame is sent. After all data frames are transmitted, the master can issue a stop condition to terminate the transmission.
**Steps of I2C Data Transmission:**
1. The master initiates the start condition by transitioning the SDA line from high to low before the SCL line shifts from high to low.
2. The master sends each slave the 7 or 10 bit address of the slave it wants to communicate with, along with the read/write bit:
3. In this step, every slave device compares the address sent by the master to its own unique address. If a match is found, the corresponding slave responds with an ACK (acknowledge) bit, signified by pulling the SDA line low for one bit. However, if the address from the master does not correspond to the slave's address, the SDA line remains high, and the slave takes no action.
4. The master sends or receives the data frame:
5. After each data frame has been transferred, the receiving device returns another ACK bit to the sender to acknowledge successful receipt of the frame:
6. To stop the data transmission, the master sends a stop condition to the slave by switching SCL high before switching SDA high:
ModusToolbox provides a set of functions and drivers for working with I2C. Here are some common I2C functions you can use:
Initialization:
cyhal_i2c_init(): Initialize the I2C peripheral with the desired pins and configuration.
Initialization:
cyhal_i2c_init(): Initialize the I2C peripheral with the desired pins and configuration.
Configuration:
cyhal_i2c_configure(): Set the I2C bus speed and other parameters.
cyhal_i2c_frequency(): Set or get the bus frequency.
Configuration:
cyhal_i2c_configure(): Set the I2C bus speed and other parameters.
cyhal_i2c_frequency(): Set or get the bus frequency.
Data Transfer:
cyhal_i2c_master_read(): Read data from a slave device.
cyhal_i2c_master_write(): Write data to a slave device.
Data Transfer:
cyhal_i2c_master_read(): Read data from a slave device.
cyhal_i2c_master_write(): Write data to a slave device.
Addressing:
cyhal_i2c_set_slave_address(): Set the address of the slave device.
cyhal_i2c_get_address(): Get the current slave address.
Addressing:
cyhal_i2c_set_slave_address(): Set the address of the slave device.
cyhal_i2c_get_address(): Get the current slave address.
Other Functions:
cyhal_i2c_is_busy(): Check if the I2C bus is busy.
cyhal_i2c_get_direction(): Get the current data transfer direction (read or write).
Other Functions:
cyhal_i2c_is_busy(): Check if the I2C bus is busy.
cyhal_i2c_get_direction(): Get the current data transfer direction (read or write).
Using I2C on PSoC 6 CY8CPROTO-062-4343WHere's a general overview of how to use I2C on the PSoC 6 board:
Hardware Setup:
- Connect your I2C devices to the I2C pins on the CY8CPROTO-062-4343W board (In my case it was 6.0 for SDA and 6.1 for SCL).
- Configure your I2C device addresses.
Project Setup:
- Create a new project in ModusToolbox and select the appropriate board and target.
- Make sure the I2C component is enabled in the project configuration go to Peripheral > Hal_I2C_Master.(you can choose for example EZI2C Master or HAL_I2C_Master, I that time I still don't understand what's the difference between the two options).
Initialize I2C:
Use cyhal_i2c_init() to initialize the I2C hardware with the desired pins.
Initialize I2C:
Use cyhal_i2c_init() to initialize the I2C hardware with the desired pins.
Configure I2C:
Use cyhal_i2c_configure() to set the desired bus speed and other parameters.
Configure I2C:
Use cyhal_i2c_configure() to set the desired bus speed and other parameters.
Data Transfer:
Use cyhal_i2c_master_read() to read data from a slave device or cyhal_i2c_master_write() to send data to a slave device.
Data Transfer:
Use cyhal_i2c_master_read() to read data from a slave device or cyhal_i2c_master_write() to send data to a slave device.
Error Handling:
- Check the return values of I2C functions for errors and handle them appropriately.
Completion and Cleanup:
- Ensure that data transfer is complete before proceeding.
- Release I2C resources with
cyhal_i2c_free()
when done.
Completion and Cleanup:
Ensure that data transfer is complete before proceeding.
Release I2C resources with cyhal_i2c_free() when done.
Here is the i2c_scanner code :
#include "cyhal.h"
#include "cybsp.h"
#include "cy_retarget_io.h"
#include <assert.h>
#include <stdio.h>
cyhal_i2c_t mI2C;
// Print the probe table
void probe()
{
cy_rslt_t rval;
// Setup the screen and print the header
printf("\n\n ");
for (unsigned int i = 0; i < 0x10; i++)
{
printf("%02X ", i);
}
// Iterate through the address starting at 0x00
for (uint32_t i2caddress = 0; i2caddress < 0x80; i2caddress++)
{
if (i2caddress % 0x10 == 0)
printf("\n%02X ", (unsigned int)i2caddress);
uint8_t buffer[1] = {0}; // You can change this to your specific data to send
rval = cyhal_i2c_master_write(&mI2C, i2caddress, buffer, 1, 0, false);
if (rval == CY_RSLT_SUCCESS) // If you get ACK, then print the address
{
printf("%02X ", (unsigned int)i2caddress);
}
else // Otherwise print a --
{
printf("-- ");
}
}
printf("\n");
}
int main(void)
{
cy_rslt_t result;
cyhal_i2c_cfg_t mI2C_cfg;
/* Initialize the device and board peripherals */
result = cybsp_init();
assert(result == CY_RSLT_SUCCESS);
/* Initialize the retarget-io */
result = cy_retarget_io_init(CYBSP_DEBUG_UART_TX, CYBSP_DEBUG_UART_RX, CY_RETARGET_IO_BAUDRATE);
assert(result == CY_RSLT_SUCCESS);
/* Clear the screen */
printf("\x1b[2J\x1b[;H");
printf("I2C Detect\n");
printf("Press p for probe, ? for help\n");
/* I2C Master configuration settings */
printf(">> Configuring I2C Master..... ");
mI2C_cfg.is_slave = false;
mI2C_cfg.frequencyhal_hz = 100000; // Replace with your desired frequency (e.g., 100 kHz)
result = cyhal_i2c_init(&mI2C, CYBSP_I2C_SDA, CYBSP_I2C_SCL, NULL);
assert(result == CY_RSLT_SUCCESS);
result = cyhal_i2c_configure(&mI2C, &mI2C_cfg);
assert(result == CY_RSLT_SUCCESS);
printf("Done\r\n\n");
/* Enable interrupts */
__enable_irq();
probe(); // Do an initial probe
while (1)
{
char c = getchar();
switch (c)
{
case 'p':
probe();
break;
case '?':
printf("------------------\n");
printf("Command\n");
printf("p\tProbe\n");
printf("?\tHelp\n");
break;
}
}
}
So here the i2c_scanner espacially the probe() function scan I2C addresses from 0x00 to 0x7F (128 possible addresses).
- For each address, it sends a test message to see if any device responds.
- If a device responds (acknowledges), it's marked with its address.
- If no response, it's marked with "--" to indicate no device at that address
Here is the results :
Here I have use arduino ide to see the serial monitor since the modustoolbox doesn't have one, of course you can use any other software that utilize the serial monitor, we can see that the scan has found 1 adress the 0x59 (you scan multiple slave).
Now here is the code for the accelerometer :
#include "cyhal.h"
#include "cybsp.h"
#include "cy_retarget_io.h"
#include <assert.h>
#include <stdio.h>
cyhal_i2c_t mI2C;
// Déclaration des adresses du module
#define ADXL345_Adresse 0x53 // adresse de l'ADXL345
#define POWER_CTL 0x2D // registre Power Control
#define DATA_FORMAT 0x31 // registre Data Format
#define DATAX0 0x32 // bit de poids faible axe X
#define DATAX1 0x33 // bit de poids fort axe X
#define DATAY0 0x34 // bit de poids faible axe Y
#define DATAY1 0x35 // bit de poids fort axe Y
#define DATAZ0 0x36 // bit de poids faible axe Z
#define DATAZ1 0x37 // bit de poids fort axe Z
// Configuration du module
#define ADXL345_Precision2G 0x00
#define ADXL345_Precision4G 0x01
#define ADXL345_Precision8G 0x02
#define ADXL345_Precision16G 0x03
#define ADXL345_ModeMesure 0x08
void writeToADXL345(uint8_t regAddress, uint8_t value) {
uint8_t buffer[2];
buffer[0] = regAddress;
buffer[1] = value;
cyhal_i2c_master_write(&mI2C, ADXL345_Adresse, buffer, 2, 0, false);
}
void readFromADXL345(uint8_t regAddress, uint8_t* data, uint8_t len) {
cyhal_i2c_master_write(&mI2C, ADXL345_Adresse, ®Address, 1, 0, false);
cyhal_i2c_master_read(&mI2C, ADXL345_Adresse, data, len, 0, false);
}
int main(void) {
cy_rslt_t result;
cyhal_i2c_cfg_t mI2C_cfg;
uint8_t buffer[6]; // stockage des données du module
int composante_X;
int composante_Y;
int composante_Z;
result = cybsp_init();
assert(result == CY_RSLT_SUCCESS);
result = cy_retarget_io_init(CYBSP_DEBUG_UART_TX, CYBSP_DEBUG_UART_RX, CY_RETARGET_IO_BAUDRATE);
assert(result == CY_RSLT_SUCCESS);
printf("\x1b[2J\x1b[;H");
printf("ADXL345 Sensor Data\n");
printf(">> Configuring I2C Master..... ");
mI2C_cfg.is_slave = false;
mI2C_cfg.frequencyhal_hz = 100000;
result = cyhal_i2c_init(&mI2C, CYBSP_I2C_SDA, CYBSP_I2C_SCL, NULL);
assert(result == CY_RSLT_SUCCESS);
result = cyhal_i2c_configure(&mI2C, &mI2C_cfg);
assert(result == CY_RSLT_SUCCESS);
printf("Done\r\n\n");
__enable_irq();
// Configure the ADXL345 module
writeToADXL345(0x31, 0x01); // Set data format to 4g
writeToADXL345(0x2D, 0x08); // Set to measurement mode
while (1) {
readFromADXL345(0x32, buffer, 6);
composante_X=(buffer[1] << 8) | buffer[0]; // élaboration des 3 composantes
composante_Y=(buffer[3] << 8) | buffer[2];
composante_Z=(buffer[5] << 8) | buffer[4];
printf("X=%d\tY=%d\tZ=%d\n", composante_X, composante_Y, composante_Z);
cyhal_system_delay_ms(1000);
}
}
Here is the results :
I'd like to Thanks cypress for sending the bundle kit and the other sponsor !
Comments