Using I2C on #STM32F411 with CMSIS

Following hot on the tail from my previous post on I2C using libopencm3, I now present one using CMSIS. I did a lot of prep work beforehand, which is why the final result was so quick. Here’s the code:

     1	#define STM32F411xE
     2	#include "stm32f4xx.h"
     3	
     4	
     5	#include <stdbool.h>
     6	
     7	/* approx delay in ms */
     8	void __attribute__((optimize("O0"))) delayish (uint32_t ms)
     9	{    
    10		uint32_t i, j;
    11		for (i=0; i<ms; i++)
    12			for (j=0; j<1330; j++) // determined using logic analyser
    13				asm("nop");
    14	}
    15	
    16	const uint8_t addr = 0x70; // set this to whatever your slave address is
    17	uint8_t ledmat_grid[8];
    18	
    19	void ledmat_show(void);
    20	
    21	static uint8_t patternX[] = { 
    22		0b10000001, 
    23		0b01000010, 
    24		0b00100100, 
    25		0b00010000,
    26		0b00001000, 
    27		0b00100100, 
    28		0b01000010, 
    29		0b10000001 
    30	};
    31	
    32	static uint8_t patternP[] = { // the letter P, with some orientation frills
    33		0b11110001, 
    34		0b10001000, 
    35		0b10001000, 
    36		0b11110000,
    37		0b10000000, 
    38		0b10000000, 
    39		0b10000001, 
    40		0b10000010 
    41	};
    42	
    43	
    44	void ledmat_send_data(uint8_t *data, int len) 
    45	{
    46		// begin i2c transaction
    47		bool read = false;
    48		int sid = 0x70;
    49		I2C1->CR1 |= I2C_CR1_START; // GENERATE A START CONDITION
    50		while (!(I2C1->SR1 & I2C_SR1_SB))
    51			;
    52		I2C1->DR = (sid << 1) + (read ? 1 : 0);
    53		while (!(I2C1->SR1 & I2C_SR1_ADDR))
    54			;
    55		I2C1->SR2;
    56	
    57		// send
    58		//bool with_btf = false;
    59		for (uint32_t i = 0; i < len; i++) {
    60			I2C1->DR = *(data + i);
    61			while (!(I2C1->SR1 & I2C_SR1_BTF)) {} ;
    62			//while(!(I2C1->SR1 & I2C_SR1_TXE));
    63			//if(with_btf)
    64			//              while(!(I2C1->SR1 & I2C_SR1_BTF)); // added mcarter 2020-10-10
    65		}
    66	
    67		// end i2c transaction
    68		I2C1->CR1 |= I2C_CR1_STOP;
    69	}
    70	
    71	
    72	void ledmat_send_cmd(uint8_t cmd)
    73	{
    74		ledmat_send_data(&cmd, 1);
    75	}
    76	
    77	void ledmat_init(void) 
    78	{
    79		ledmat_send_cmd(0x20 | 1); // turn on oscillator
    80		ledmat_show(); // clear out any display that is there
    81		ledmat_send_cmd(0x81); // display on
    82		ledmat_send_cmd(0xE0 | 0); // brightness to dimmest (but you should probably set it)
    83	
    84	}
    85	
    86	void ledmat_set(uint8_t row, uint8_t col, bool on) 
    87	{
    88		// the LED has a strange co-ordinate system
    89		int pos1 = 7 - col; // actually a "row"
    90		int pos2 = row - 1;
    91		if (pos2 == -1)
    92			pos2 = 7;
    93		ledmat_grid[pos1] &= ~(1 << pos2);
    94		if (on)
    95			ledmat_grid[pos1] |= (1 << pos2);
    96	
    97	}
    98	
    99	void ledmat_show(void) 
   100	{
   101		for (int r = 0; r < 8; r++) {
   102			uint8_t data[2];
   103			data[0] = r * 2;
   104			data[1] = ledmat_grid[r];
   105			ledmat_send_data(data, 2);
   106		}
   107	
   108	}
   109	
   110	
   111	static void i2c_setup(void)
   112	{
   113	
   114		RCC->AHB1ENR	|= RCC_AHB1ENR_GPIOBEN;
   115	
   116		GPIO_TypeDef *GPIO = GPIOB; // unneeded abstraction in this case
   117	
   118		//PB6
   119		GPIO->MODER 	|= GPIO_MODER_MODER6_1; // Alt fn mode
   120		GPIO->AFR[0] 	|= (4 << GPIO_AFRL_AFSEL6_Pos) ; // Alt fn 4, being I2C
   121		GPIO->PUPDR |= (1 << GPIO_PUPDR_PUPD6_Pos); // pullup (may not be relevant)
   122		GPIO->OTYPER |= (1<< 6); // open drain. Important!
   123		// PB7
   124		GPIO->MODER 	|= GPIO_MODER_MODER7_1; // Alt fn mode 
   125		GPIO->AFR[0] 	|= (4 << GPIO_AFRL_AFSEL7_Pos) ; // alt fn 4 again
   126		GPIO->PUPDR |= (1 << GPIO_PUPDR_PUPD7_Pos); // pullup (may not be relevant)
   127		GPIO->OTYPER |= (1<< 7); // open drain Important!
   128	
   129		RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
   130		I2C1->CR2 = 16; // works
   131		I2C1->TRISE = 17;
   132		I2C1->CCR = 80;
   133		I2C1->CR1 |= I2C_CR1_PE;
   134	}
   135	
   136	int main(void)
   137	{
   138		i2c_setup();
   139		ledmat_init();
   140		//initalise display
   141		for (int r = 0; r < 8; r++) {
   142			uint8_t row = patternX[r];
   143			for (int c = 0; c < 8; c++) {
   144				ledmat_set(r, c, row >> 7);
   145				row <<= 1;
   146			}
   147		}
   148	
   149	
   150		while(1) {
   151			ledmat_show();
   152			delayish(1);
   153		}
   154	
   155	}
   156	

In the end, the code isn’t too bad.

Sending data is reasonably straightforward. You generate a start condition, wait for acknowledgement, send the address, and wait for acknowledgement.

I’m assume 7-bit address (sid). You need to left-shift the address. Add 1 if it is a read instruction.

Then you need to send each datum, waiting for the BTF flag to be raised so that you can send another one.

When that’s all done, you send the stop condition.

There may be subtle bugs in this code, like a need to wait for acknowledgement of the stop condition, or the availability of the device in the first place

It’s a start, anyway.

Reading data is a bit trickier, I think. The last byte must be handled differently than the previous ones.

Plus there’s whole hoopla regarding acknowledgement failures and all that.

But what I’ve got at least works. If you know a better way, then let me know.

Setting up I2C isn’t too bad either, once you’ve sorted out some of the subtleties. You need to activate the relevant GPIO RCC (I2C1 uses port B), then set up PB6 and PB7 into alternative function 4. Create them as pullups (although I’m not sure that’s right. It’s how the HAL does it, anyway) and make sure they’re open-drain. My failure to do this soaked up a lot of my time trying to figure out what the problem was.

Then you need to activate the I2Cx clock. In lines 130 to 132 I’ve set some “magic numbers”, which you can puzzle out for yourself. It mosts related to clock and pin speed.

The remainder of the code deals with the protocol of the MAX7219, which is reasonably straightforward.

I hope that gives you at least a kicking-off point to explore. I2C has a lot of extra nuance that the likes of SPI doesn’t have.

Code 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