How to read data result from light sensor using interrupts - c

I'm using Arduino mega 2560 and light sensor BH1750FVI. to show result i'm using minicom, work on Ubuntu 16.04 LTS
I write code and it's work and send me data result reading from light sensor, i can read info only once. I don't understand how i make it work through interrupts (TWI_vect) and display the results every time using my uart.
#include "my_header.h"
#define I2C_STATUS_MASK 0xF8
#define START_COND_TRANSMITTED 0x08
#define REPEATED_START_COND_TRANSMITTED 0x10
#define SLA_W_TRANSMITTED_ACK_RECEIVED 0x18
#define DATA_TRANSMITTED_ACK_RECEIVED 0x28
#define SLA_R_TRANSMITTED_ACK_RECEIVED 0x40
#define DATA_RECEIVED_ACK_RETURNED 0x50
#define DATA_RECEIVED_NACK_RETURNED 0x58
volatile int light_intensity = 0; // var for read data from sensor
ISR(USART0_UDRE_vect) {
if (!bufferIsEmpty(&buffer)) //if we have something to read do it
UDR0 = popFromBuff(&buffer);
else
UCSR0B &= ~_BV(UDRIE0); //disallow interrupts
}
int main(void) {
cli();
init_port(); //initialize my port
init_buffer(&buffer, BUFF_SIZE); //init ring_buffer for uart
init_uart();
TWI_init();
sei();
TWI_start(); //send start
TWI_send_SLA(WRITE); //send SLA+W to light sensor
TWI_sendData(0b00010000); //opecode for Measurement
TWI_stop(); //stop
TWI_start(); //send start
TWI_send_SLA(READ); // send SLA+R to sensor
TWI_readData(); // read data from it light_intensity = TWDR
TWI_stop(); // stop
u_printnumbers(light_intensity); /*my func that send data to ring_buffer and later
*using interrupts send it form buffer to UDR
*/
u_print("\n");
while (1) {
if (!bufferIsEmpty(&buffer)) /*if buffer is not empty allow interrupts for uart*/
UCSR0B |= _BV(UDRIE0);
}
}
So this code work and i receive data from sensor and all fine. But i want using it with interrupts (TWI_vect) and receive data all the time from sensor like in while(). i read a lot of info but don't understand how it should look.
Can you show me the right way using my code with interrupts (TWI_vect) some little example. Thx for help!

To use the Two-Wirte interface (I²C; TWI) interrupt-driven, you will have to implement some sort of state machine inside the TWI ISR.
Atmel Application Note AVR135 give a slight idea how this can be done. You will have to have buffers ready and abstract transactions into a struct of some sort. For example, you can have a
struct i2c_transfer {
uint8_t i2c_sla; /* slave address */
const uint8_t *txbuf;
size_t txbytes;
uint8_t *rxbuf;
size_t rxbytes;
}
That you set up, then have a function start transmitting I2C and check if you're done via checking if txbytes == 0 && rxbytes == 0 or an error occurred. Your ISR would then need to check its state and act appropriately depending on the number of bytes it's supposed to write and read.

Related

Forwarding / passthrough UART from one to another port

I'm pretty new to programming in C, but getting used to registers and the way communication in C works. Since UART using the official Arduino read() / write() creates a high delay in passing commands through, I tried to translate this Arduino sketch into pure C. (and because I've time to play around, haha)
My ebike's controller and display are communicating using UART. I tried to read the commands and react to speed changes or brake signals, but first of all I need to get rid of this huge delay in updating the display.
I'm using an Robodyn Mega 2560 PRO (Embed), which has 4x hardware serial ports. In the first step I tried to read what's coming in and forward it to the other port. Shouldn't be to hard to implement in C, right?
void setup() {
Serial1.begin(1200); // BBSHD controller #RX18 TX19
Serial2.begin(1200); // Display DP-C18 #TX16 RX17
}
void loop() {
if (Serial2.available()) {
Serial1.write(Serial2.read());
}
if (Serial1.available()) {
Serial2.write(Serial1.read());
}
}
That's what I programmed in Atmel Studio 7 so far. I used the C example from ATmega640/1280/1281/2560/2561 - Complete Datasheet (Search for USART_Receive and USART_Transmit) and this guide Simple Serial Communications With AVR libc
Currently I can compile and flash it the 2560, but the communication is not being forwarded. I don't know what the default settings are, that Arduino with Serial uses. Which mode, number of stop bits, .. Is there anything obvious I'm missing?
#define F_CPU 16000000UL
#define BAUD 1200
#include <avr/io.h>
#include <stdio.h>
#include <util/setbaud.h>
void uart_init(void) {
/* Serial1 controller */
UBRR1H = UBRRH_VALUE;
UBRR1L = UBRRL_VALUE;
/* Serial2 display */
UBRR2H = UBRRH_VALUE;
UBRR2L = UBRRL_VALUE;
/* 8-bit data, 1stop bit*/
UCSR1C = (1<<UCSZ11) | (1<<UCSZ10);
UCSR2C = (1<<UCSZ21) | (1<<UCSZ20);
/* Enable RX and TX */
UCSR1B = (1<<RXEN1) | (1<<TXEN1);
UCSR2B = (1<<RXEN2) | (1<<TXEN2);
}
int main(void) {
uart_init();
while(1) {
if (!(UCSR1A & (1<<RXC1))) { /* Only if data is received */
while (!(UCSR1A & (1<<UDRE1))); /* Wait for empty transmit buffer */
UDR2 = UDR1; /* Put data into buffer, sends the data */
}
if (!(UCSR2A & (1<<RXC2))) { /* Only if data is received */
while (!(UCSR2A & (1<<UDRE2))); /* Wait for empty transmit buffer */
UDR1 = UDR2; /* Put data into buffer, sends the data */
}
}
}

PuTTY not sending data to AVR serial

in an exercise for my embedded programming course we have to program an Atmega328p AVR chip to receive data through the serial port. We have to do this by calling a function that waits until it receives a char. Next it should display the ascii value of that char in led lights, but I am having trouble even receiving it. I've done a lot of debugging and I think I narrowed it down to PuTTY not even sending the data, or the AVR not receiving it properly. I will put my code in below:
/*
From the PC should come a char, sent via the serial port to the USART data register.
It will arrive in the RXB, which receives data sent to the USART data register.
While running the loop, the program should encounter a function that is called and waits for the RXB to be filled.
Then it will read the RXB and return it to the main loop.
The result will be stored and processed accordingly.
*/
#define F_CPU 15974400
#include <util/delay.h>
#include <avr/io.h>
#include <stdlib.h>
#include <avr/interrupt.h>
void writeChar(char x);
void initSerial();
char readChar();
int main(void)
{
initSerial();
while (1)
{
char c = readChar(); //reads char, puts it in c
_delay_ms(250); //waits
writeChar(c); // spits that char back into the terminal
}
}
void initSerial(){
UCSR0A = 0;
//UCSR0B = (1 << TXEN0); // Enable de USART Transmitter
UCSR0B = 0b00011000; //transmit and receive enable
//UCSR0C = (1 << UCSZ01) | (0 << UCSZ00); /* 8 data bits, 1 stop bit */
UCSR0C = 0b00100110; // Even parity, 8 data bits, 1 stop bit
UBRR0H=00;
UBRR0L=103; //baudrade 9600 bij
}
void writeChar(char x){
while(!(UCSR0A & (1 << UDRE0))); // waits until it can send data
UDR0 = x; // Puts x into the UDR0, outputting it
}
char readChar(){
while (!(UCSR0A & (1 << RXC0))); //waits until it can send data
return UDR0; // returns the contents of the UDR0 (the receiving part of course
}
The problem is that when I enter anything in PuTTY (that I assume I set up correctly. https://prnt.sc/rc7f0f and https://prnt.sc/rc7fbj seem to be the important screens.
Thanks in advance, I am completely out of ideas.
I fixed it myself. I figured it out while taking it downstairs to test it on another laptop. I still had LEDs put in all pins of PORTD, all on input mode (that's the default mode). I quick look at the Atmega328p user guide (section 2.5.3) revealed that the pin D0 was actually the RxD in for the USART. By putting an LED on it and effectively grounding it, it was always pulled low, and would never be put high by the CPU, which would stop the while loop check while (!(UCSR0A & (1 << RXC0))); //waits until it can send data in readChar();
So by simply removing that led it would work again. Obviously that would mean it was floating, so I set the DDRD to all be output, as nothing needed to be input anyway.
That ended up fixing it.

Using Interrupt to Transmit via USART on AVR MCU

I believe I understand how to use interrupts to receive serial data on UART of an ATmega328p, but I don't understand the mechanics of how to transmit data.
Here is a basic program that I want to use to transmit the character string "hello" using interrupts to drive transmission. I understand that the character 'o' will likely be transmitted twice, and I am ok with that.
#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 16000000UL
#define BAUD 19200
#define DOUBLE_SPEED 1
void initUART(unsigned int baud, unsigned int speed);
volatile uint8_t charIndex = 0;
volatile unsigned char command[5] = "hello";
int main(void)
{
//initialize UART
initUART(BAUD, DOUBLE_SPEED);
sei();
//What do I put here to initiate transmission of character string command?
//Is this even correct?
UDR0 = command[0];
while(1)
{
}
}
ISR(USART_TX_vect)
{
// Transmit complete interrupt triggered
if (charIndex >= 4)
{
//Reach the end of command, end transmission
return;
}
//transmit the first char or byte
UDR0 = command[charIndex];
//Step to the next place of the command
charIndex++;
}
void initUART(unsigned int baud, unsigned int speed)
{
unsigned int ubrr;
if(speed)
{
//double rate mode
ubrr = F_CPU/8/baud-1;
//set double speed mode
UCSR0A = (speed << U2X0);
}
else
{
//normal rate mode
ubrr = F_CPU/16/baud-1;
}
//set the baud rate
UBRR0H = (unsigned char)(ubrr >> 8);
UBRR0L = (unsigned char)(ubrr);
//enable Tx and Rx pins on MCU
UCSR0B = (1 << RXEN0) | (1 << TXEN0);
//enable transmit interrupt
UCSR0B = (1 << TXCIE0);
//set control bits, 8 bit char, 0 stop, no parity
UCSR0C = (1 <<UCSZ00) | (1 <<UCSZ01);
}
My understanding is that if I wrote the first character to UDR0 (as I did in main()), this would then trigger a Transmit Complete Interrupt, and then the next byte would be transmitted via the ISR. This does not seem to work.
The code shown here compiles using gcc. Can someone offer an explanation?
The key thing to understand is that the USART has 2 separate hardware registers that are used in the data transmission: UDRn and the Transmit Shift Register, which I'll just call TSR from now on.
When you write data to UDRn, assuming no tx is in progress, it'll get moved to the TSR immediately and the UDRE irq fires to tell you that the UDRn register is "empty". Note that at this point the transmission has just started, but the point is that you can already write the next byte to UDRn.
When the byte has been fully transmitted, the next byte is moved from UDRn to TSR and UDRE fires again. So, you can write the next byte to UDRn and so on.
You must only write data to the UDRn when it is "empty", otherwise you'll overwrite the byte it's currently storing and pending transmission.
In practice, you don't usually mind about the TXC irq, you want to work with the UDRE to feed more data to the USART module.
The TXC irq, however, is useful if you need to perform some operation when the transmission has actually completed. A common example when dealing with RS485 is to disable the transmitter once you're done sending data and possibly re-enable the receiver that you could have disabled to avoid echo.
Regarding your code
Your main issue is that you're setting UCSR0B 2 times in initUART() and the second write clears the bits you just set, so it's disabling the transmitter. You want to set all bits in one go, or use a |= on the second statement.

SPI slave read data into buffer on stm32?

I'm trying to set communication between esp32 (master) and stm32 (slave) over SPI. esp32 is running under micropython and sends four bytes, for example
spi.write_readinto(b'\x31\x32\x33\x34', buf)
stm32' code is here (instead of this i use SPI_InitDef.SPI_NSS = SPI_NSS_Soft;)
void SPI_Init(void) {
...
// initialize SPI slave
// for slave, no need to define SPI_BaudRatePrescaler
SPI_InitDef.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitDef.SPI_Mode = SPI_Mode_Slave;
SPI_InitDef.SPI_DataSize = SPI_DataSize_8b; // 8-bit transactions
SPI_InitDef.SPI_FirstBit = SPI_FirstBit_MSB; // MSB first
SPI_InitDef.SPI_CPOL = SPI_CPOL_Low; // CPOL = 0, clock idle low
SPI_InitDef.SPI_CPHA = SPI_CPHA_2Edge; // CPHA = 1
SPI_InitDef.SPI_NSS = SPI_NSS_Hard; // use hardware SS
SPI_InitDef.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64; // APB2 72/64 = 1.125 MHz
SPI_InitDef.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitDef);
SPI_Cmd(SPI1, ENABLE);
NVIC_EnableIRQ(SPI1_IRQn);
//Тут мы разрешаем прерывание по приему
SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_RXNE, ENABLE);
}
void main() {
/* Setup SysTick Timer for 10ms interrupts */
if (SysTick_Config(SystemCoreClock / 100))
{
/* Capture error */
while (1);
}
/* Configure the SysTick handler priority */
NVIC_SetPriority(SysTick_IRQn, 0x0);
SPI_Init();
while(1) {
while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE));
for (u8 i=0; i<4; i++) {
printf("0x%02x ", SPI_I2S_ReceiveData(SPI1));
}
printf("\r\n");
}
}
But when I send four bytes 0x31 0x32 0x33 0x34 (analyzer confirms bytes were sent) and my stm gets only 0x31 0x32 0x31 0x32
UPD
I use std periph library and SPI_I2S_ReceiveData is a native method to read byte from SPI.
uint16_t SPI_I2S_ReceiveData ( SPI_TypeDef * SPIx )
Returns the most recent received data by the SPIx/I2Sx peripheral.
Parameters:
SPIx,: To select the SPIx/I2Sx peripheral, where x can be: 1, 2 or 3 in SPI mode or 2 or 3 in I2S mode or I2Sxext for I2S full duplex mode.
Return values:
The value of the received data.
uint16_t SPI_I2S_ReceiveData ( SPI_TypeDef * SPIx )
Returns the most recent received data by the SPIx/I2Sx peripheral.
Parameters:
SPIx,: To select the SPIx/I2Sx peripheral, where x can be: 1, 2 or 3 in SPI mode or 2 or 3 in I2S mode or I2Sxext for I2S full duplex mode.
Return values:
The value of the received data.
But maybe I exit out from IRQ before all data are read. I found to run the while loop until the transmission of the last byte is complete
I think the following code is not correct (but I don't know what the function SPI_I2S_ReceiveData is doing):
while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE));
for (u8 i=0; i<4; i++) {
printf("0x%02x ", SPI_I2S_ReceiveData(SPI1));
}
You exit from the while as soon as one byte is ready to be read. I assume SPI_I2S_ReceiveData is only reading the SPI FIFO. in that case you try to read 4 bytes when possibly only one or two has been received.
You didn't precise the kind of STM32 you're using so I am describing the SPI of STM32H7 (as far as I know it should be pretty similar in other STM32).
To setup a reception in slave mode you should define in particular these 3 parameters:
the length of the socalled "frame" (number of bytes to be read/written at once). This is the field SPI_DataSize` in the HAL data structure, here 8 bits.
the number of transfer (TSIZE) which specifies when the End Of Transmission event is generated. It is expressed in number of "frames". This parameter must be set through register SPI.CR2 before each reception (provided you know the number of bytes to be received of course).
the "FIFO threshold". It specifies at which frequency an event RXP or TXP is generated. You can change this parameter to decrease the workload on the software but to receive only 4 bytes it has no impact.
In your case I think you should setup a transfer size of 4 (4 bytes) and wait for EOT flag to be set. When it is set you only have to read 4 bytes from SPI Receive Register (you can read all 4 bytes at once by the way).
I suggest you do not use the HAL but write your own SPI reception / transmission routines by reading / writing registers. It is not a very complex peripheral (so it will not cost you a lot of time) and you will understand precisely how it works (instead of digging into the HAL).

Multi-interrupt for real-time data logging with MCU-ATMega 1280

My question is about real time data logging and multi-interrupt.
I am trying to program an MCU-ATMega 1280 by winAVR to make it read the pulses from the quadrature encoder(20um/pitch) and store the data into the flash memory (Microchip SST25VF080B, serial SPI protocol). After the encoder finishes its operation (around 2 minutes), the MCU export the data from the memory to the screen. My code is below.
But I don't know why it is not run correctly. There are 2 kind of bugs: one bug is some points suddenly out of the trend, another bug is sudden jumping value although the encoder runs slowly. The jumping seems to appear only when there is a turn.
I think the problem may lie only in the data storing because the trend happens like what I expected except for the jumps. I just want to ask if I run both ISR like what I did in the program. is there a case a ISR will be intervened by another ISR when it is running? According to atmega 1280 datasheet, it seems that when one ISR is occurring, no other interrupt allow to happen after the previous interrupt finish its routine.
#include <stdlib.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "USART.h" // this header is for viewing the data on the computer
#include "flashmemlib.h" // this header contains the function to read n
//write on the memory
#define MISO PB3
#define MOSI PB2
#define SCK PB1
#define CS PB0
#define HOLD PB6
#define WP PB7
#define sigA PD0 //INT0
#define sigB PD2 //INT2
#define LED PD3
uint8_t HADD,MADD,LADD, HDATA, LDATA,i; //HADD=high address, MADD-medium address, LADD-low address
volatile int buffer[8]; //this buffer will store the encoder pulse
uint32_t address = 0;
uint16_t DATA16B = 0;
int main(void)
{
INITIALIZE(); //initialize the IO pin, timer CTC mode, SPI and USART protocol
for(i=0;i<8;i++)
buffer[i]=0;
sei();
//AAI process- AAI is just one writing mode of the memory
AAIInit(address,0);
while (address < 50) //just a dummy loop which lasts for 5 secs (appox)
{
_delay_ms(100);
address++;
}
AAIDI();//disable AAI process
cli(); //disable global interrupt
EIMSK &= ~(1<<INT0);
TIMSK1 &= ~(1<<OCIE1A);
//code for reading procedure. i thought this part is unnecessary because i am quite //confident that it works correcly
return (0);
}
ISR(INT0_vect) // this interrupt is mainly for counting the number of encoder's pulses
{ // When an interrupt occurs, we only have to check the level of
// of pB to determine the direction
PORTB &= ~(1<<HOLD);
for(i=0;i<8;i++)
buffer[i+1]=buffer[i];
if (PIND & (1<<sigB))
buffer[0]++;
else buffer[0]--;
PORTB |= (1<<HOLD);
}
ISR(TIMER0_COMPA_vect) //after around 1ms, this interrupt is triggered. it is for storing the data into the memory.
{
HDATA =(buffer[7]>>8)&0xFF;
LDATA = buffer[7]&0xFF;
PORTB &= ~(1<<CS);
SEND(AD);
SEND(HDATA);
SEND(LDATA);
PORTB |=(1<<CS);
}
void SEND(volatile uint8_t data)
{
SPDR = data; // Start the transmission
while (!(SPSR & (1<<SPIF))){} // Wait the end of the transmission
}
uint8_t SREAD(void)
{
uint8_t data;
SPDR = 0xff; // Start the transmission
while (!(SPSR & (1<<SPIF))){} // Wait the end of the transmission
data=SPDR;
return data;
}
In the Interrupt Service Routine of INT0 you are writing:
for(i=0;i<8;i++)
buffer[i+1]=buffer[i];
Means that when i=7 you are writing outside of the array's predetermined space, and probably overwriting another variable. So you have to do:
for(i=0;i<7;i++)
buffer[i+1]=buffer[i];
AVR will manage the interrupts as you described, based on the ATmega1280 datasheet. Alternatively, if you want to allow the interruption of an ISR vector by another interrupt you need to do as follows (example):
ISR(INT0_vect, ISR_NOBLOCK)
{...
...}

Resources