Using I2C on #STM32F411 with libopencm3

I finally got I2C working on my black pill with libopencm3. Urgh! I interface with an Adafruit Mini 8×8 LED matrix. I think it uses a MAX7219 to control the display, which is the same IC used in 8-digit LED displays. Correct me if I’m wrong. The Adafruit matrix does seem to have a quirk to it though. The ordering of the bits within a byte don’t correspond to the same order as the LEDs on the matrix, so some bit-twiddling is required to get the display to look how you want it to.

An LED matrix is a good device with which to experiment with I2C, because it is simplicity. It much easier than an SSD1306 display, for example, where there’s more hoopla involved in setting up and displaying output.

I’m just going to present the code in all its gory, messy, detail:

     1	/*
     2	 * Memory-to-memory must use DMA2
     3	 *
     4	 * The F4 has 2 controllers: DMA1, and DMA2.
     5	 * Each DMA has 8 streams: pathways where memory can flow
     6	 *
     7	 * Further info:
     8	 * https://adammunich.com/stm32-dma-cheat-sheet/
     9	 */
    10	
    11	#include <libopencm3/stm32/rcc.h>
    12	#include <libopencm3/stm32/gpio.h>
    13	//#include <libopencm3/stm32/timer.h>
    14	//#include <libopencm3/stm32/spi.h>
    15	//#include <libopencm3/stm32/dma.h>
    16	#include <libopencm3/stm32/i2c.h>
    17	
    18	#include <string.h>
    19	
    20	#include "mal.h"
    21	
    22	typedef uint32_t u32;
    23	
    24	#define LED PC13
    25	
    26	
    27	static void myputs(const char *str)
    28	{
    29		mal_usart_print(str);
    30		mal_usart_print("\r\n");
    31	}
    32	
    33	
    34	void nop(void);
    35	void nop(void)
    36	{
    37		__asm__ volatile ("nop");
    38	}
    39	
    40	
    41	const uint8_t addr = 0x70; // set this to whatever your slave address is
    42	uint8_t ledmat_grid[8];
    43	
    44	void ledmat_show(void);
    45	
    46	static uint8_t patternX[] = { 0b10000001, 0b01000010, 0b00100100, 0b00010000,
    47	
    48		0b00001000, 0b00100100, 0b01000010, 0b10000001 };
    49	
    50	static uint8_t patternP[] = { // the letter P, with some orientation frills
    51		0b11110001, 0b10001000, 0b10001000, 0b11110000,
    52	
    53		0b10000000, 0b10000000, 0b10000001, 0b10000010 };
    54	
    55	
    56	void ledmat_send_data(uint8_t *data, int len) 
    57	{
    58	#if 1
    59		//i2c_transfer7(I2C1, addr, 0, 0, data, len);
    60		//i2c_transfer7(I2C1, addr<<1, data, len, 0 , 0);
    61		i2c_transfer7(I2C1, addr, data, len, 0 , 0);
    62		//HAL_I2C_Master_Transmit(&hi2c1, 0x70<<1, data, len, 100);
    63	#else
    64	
    65		// begin i2c transaction
    66		bool read = false;
    67		int sid = 0x70;
    68		I2C1->CR1 |= I2C_CR1_START; // GENERATE A START CONDITION
    69		while (!(I2C1->SR1 & I2C_SR1_SB))
    70			;
    71		I2C1->DR = (sid << 1) + (read ? 1 : 0);
    72		while (!(I2C1->SR1 & I2C_SR1_ADDR))
    73			;
    74		I2C1->SR2;
    75	
    76		// send
    77		//bool with_btf = false;
    78		for (uint32_t i = 0; i < len; i++) {
    79			I2C1->DR = *(data + i);
    80			while (!(I2C1->SR1 & I2C_SR1_BTF))
    81				;
    82			//while(!(I2C1->SR1 & I2C_SR1_TXE));
    83			//if(with_btf)
    84			//              while(!(I2C1->SR1 & I2C_SR1_BTF)); // added mcarter 2020-10-10
    85		}
    86	
    87		// end i2c transaction
    88		I2C1->CR1 |= I2C_CR1_STOP;
    89	#endif
    90	}
    91	
    92	
    93	void ledmat_send_cmd(uint8_t cmd)
    94		//void LedMat::send_cmd(uint8_t cmd)
    95	{
    96		//i2c_write_blocking(m_i2c_inst, m_addr, &cmd, 1, false);
    97		//HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
    98		ledmat_send_data(&cmd, 1);
    99	}
   100	
   101	void ledmat_init(void) 
   102	{
   103		ledmat_send_cmd(0x20 | 1); // turn on oscillator
   104		ledmat_show(); // clear out any display that is there
   105		ledmat_send_cmd(0x81); // display on
   106		ledmat_send_cmd(0xE0 | 0); // brightness to dimmest (but you should probably set it)
   107	
   108	}
   109	
   110	void ledmat_set(uint8_t row, uint8_t col, bool on) 
   111	{
   112		// the LED has a strange co-ordinate system
   113		int pos1 = 7 - col; // actually a "row"
   114		int pos2 = row - 1;
   115		if (pos2 == -1)
   116			pos2 = 7;
   117		//m_grid[pos1].set(pos2, on);
   118		ledmat_grid[pos1] &= ~(1 << pos2);
   119		if (on)
   120			ledmat_grid[pos1] |= (1 << pos2);
   121	
   122	}
   123	
   124	void ledmat_show(void) 
   125	{
   126		for (int r = 0; r < 8; r++) {
   127			uint8_t data[2];
   128			data[0] = r * 2;
   129			//data[1] = m_grid[r].to_ulong();
   130			data[1] = ledmat_grid[r];
   131			ledmat_send_data(data, 2);
   132		}
   133	
   134	}
   135	
   136	
   137	static void i2c_setup(void)
   138	{
   139		rcc_periph_clock_enable(RCC_GPIOB);
   140		//rcc_periph_clock_enable(RCC_GPIOH);
   141		//rcc_set_i2c_clock_hsi(I2C1);
   142	
   143		i2c_reset(I2C1);
   144		/* Setup GPIO pin GPIO_USART2_TX/GPIO9 on GPIO port A for transmit. */
   145		//gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO6 | GPIO7);
   146		gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO6 | GPIO7);
   147		//gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLDOWN, GPIO6 | GPIO7);
   148	
   149		// it's important to set the pins to open drain
   150		gpio_set_output_options(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_25MHZ, GPIO6 | GPIO7);
   151		gpio_set_af(GPIOB, GPIO_AF4, GPIO6 | GPIO7);
   152		rcc_periph_clock_enable(RCC_I2C1);
   153		i2c_peripheral_disable(I2C1);
   154		//configure ANFOFF DNF[3:0] in CR1
   155		//i2c_enable_analog_filter(I2C1);
   156		//i2c_set_digital_filter(I2C1, 0);
   157		/* HSI is at 8Mhz */
   158		i2c_set_speed(I2C1, i2c_speed_sm_100k, 8);
   159		//configure No-Stretch CR1 (only relevant in slave mode)
   160		//i2c_enable_stretching(I2C1);
   161		//addressing mode
   162		//i2c_set_7bit_addr_mode(I2C1);
   163		i2c_set_standard_mode(I2C1); // mcarter added 2021-11-23
   164		i2c_peripheral_enable(I2C1);
   165	}
   166	
   167	int main(void)
   168	{
   169		pin_out(LED);
   170		mal_usart_init();
   171	
   172		myputs("");
   173		myputs("=============================");
   174		myputs("I2C example: master");
   175	
   176	#if 1
   177		i2c_setup();
   178	#else
   179		rcc_periph_clock_enable(RCC_I2C1);
   180		rcc_periph_clock_enable(RCC_GPIOB);
   181		//i2c_reset(I2C1);
   182		gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO6 | GPIO7);
   183		gpio_set_af(GPIOB, GPIO_AF4, GPIO6 | GPIO7);
   184		//i2c_peripheral_disable(I2C1);
   185		//i2c_enable_ack(I2C1);
   186		i2c_peripheral_enable(I2C1);
   187	#endif
   188		uint8_t data;
   189	
   190		ledmat_init();
   191		//initalise display
   192		for (int r = 0; r < 8; r++) {
   193			uint8_t row = patternX[r];
   194			for (int c = 0; c < 8; c++) {
   195				ledmat_set(r, c, row >> 7);
   196				row <<= 1;
   197			}
   198		}
   199	
   200	
   201		while(1) {
   202			ledmat_show();
   203	
   204			nop();
   205			mal_delayish(1);
   206			pin_toggle(LED);
   207		}
   208	
   209	}

The polite thing to do would be to rip out all the junk and present a clean solution, but I am too lazy for that. Ignore the UART stuff and the “mal”. Just rip it out for your testing purposes.

Although my code is ugly, it at least works. It has the barebones of what is required to use your mcu as a master for an I2C device. The thing that was tripping me up for a long time is that I hadn’t set the SDA and SCL pins to open drain. So the device didn’t work.

I ended up using Cube and HAL to get the device working, and then painstakingly inlining code so that I could see what was going on under the hood. There were some interesting revelations, like HAL enables the I2C RCC during configuration of GPIO. Shortly afterwards it disables it again so that it can configure I2C. Hmmm, OK.

The good news is that HAL does at least configure the mcu according to requirements nice and graphically. If a little convoluted.

I have elected to take a more direct path. The HAL undoubtedly provides a more robust solution, but in general, most people aren’t going to need anything fancy. You’re most likely to just want to set up I2C, get it running, without bothering with the intricacies of disabling it later. Maybe it’s something you’d be worried about if power consumption was an important feature, but for most people, all this fancy stuff is overkill.

Actually, one of the problems of libopencm3, I find, is that it’s not exactly a hardware abstraction layer. There are subtle difference between mcus, even within the STM32 family for example, meaning that code is not necessarily entirely portable.

Libopencm3 is useful as reference material, though. I’m ready to have a crack at writing I2C drivers in CMSIS.

I think the same could be said with RIOT, the RTOS. I’m trying to give it some love, but it’s not giving me much love back. I tried porting my code to it, without luck. There’s a function called i2c_init(), which should, surprise surprise, initialise the I2C device. It knows about what pins to use for SDA and SCL, but I’m not sure if I need to set them up separately. My code doesn’t work, let me put it this way.

They could really do with some simple examples. I find myself having to dig through the code to find out what’s going on. MCUs often have nuanced issues, and some subtle misconfiguration might mean that won’t work. RIOT really needs to provide some documentation on this front, I feel, so that we know what to expect, or what not to.

Maybe Rust is the answer to many of these problems as, in theory, the APIs could be designed so that misconfigurations can’t happen. I’ve never been able to get a Rust project to successfully compile and flash to an MCU, however. Hmmm.

So, that’s my little rant for the day, then. I’m off to investigate I2C on CMSIS, which I’m fairly confident that I’ll be able to get going successfully.

Code for this project is here.

About mcturra2000

Computer programmer living in Scotland.
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s